feat(#1): Set up monorepo scaffold with pnpm workspaces + TurboRepo
Implements the foundational project structure including: - pnpm workspaces configuration - TurboRepo for build orchestration - NestJS 11.1.12 API (apps/api) - Next.js 16.1.6 web app (apps/web) - Shared packages (config, shared, ui) - TypeScript strict mode configuration - ESLint + Prettier setup - Vitest for unit testing (19 passing tests) Fixes #1 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
18
apps/api/.swcrc
Normal file
18
apps/api/.swcrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "es2022",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true
|
||||
},
|
||||
"transform": {
|
||||
"legacyDecorator": true,
|
||||
"decoratorMetadata": true
|
||||
},
|
||||
"keepClassNames": true
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
8
apps/api/nest-cli.json
Normal file
8
apps/api/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
40
apps/api/package.json
Normal file
40
apps/api/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@mosaic/api",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "nest start --watch",
|
||||
"start": "node dist/main",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:e2e": "vitest run --config ./vitest.e2e.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.1.12",
|
||||
"@nestjs/core": "^11.1.12",
|
||||
"@nestjs/platform-express": "^11.1.12",
|
||||
"@mosaic/shared": "workspace:*",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mosaic/config": "workspace:*",
|
||||
"@nestjs/cli": "^11.0.6",
|
||||
"@nestjs/schematics": "^11.0.1",
|
||||
"@nestjs/testing": "^11.1.12",
|
||||
"@swc/core": "^1.10.18",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/node": "^22.13.4",
|
||||
"typescript": "^5.8.2",
|
||||
"unplugin-swc": "^1.5.2",
|
||||
"vitest": "^3.0.8"
|
||||
}
|
||||
}
|
||||
14
apps/api/src/app.controller.d.ts
vendored
Normal file
14
apps/api/src/app.controller.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import { AppService } from "./app.service";
|
||||
import type { ApiResponse } from "@mosaic/shared/types";
|
||||
interface HealthStatus {
|
||||
status: string;
|
||||
timestamp: string;
|
||||
}
|
||||
export declare class AppController {
|
||||
private readonly appService;
|
||||
constructor(appService: AppService);
|
||||
getHello(): string;
|
||||
getHealth(): ApiResponse<HealthStatus>;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=app.controller.d.ts.map
|
||||
1
apps/api/src/app.controller.d.ts.map
Normal file
1
apps/api/src/app.controller.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.controller.d.ts","sourceRoot":"","sources":["app.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBACa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,UAAU;IAGnD,QAAQ,IAAI,MAAM;IAKlB,SAAS,IAAI,WAAW,CAAC,YAAY,CAAC;CASvC"}
|
||||
50
apps/api/src/app.controller.js
Normal file
50
apps/api/src/app.controller.js
Normal file
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AppController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const app_service_1 = require("./app.service");
|
||||
let AppController = class AppController {
|
||||
appService;
|
||||
constructor(appService) {
|
||||
this.appService = appService;
|
||||
}
|
||||
getHello() {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
getHealth() {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
status: "healthy",
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
exports.AppController = AppController;
|
||||
__decorate([
|
||||
(0, common_1.Get)(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", String)
|
||||
], AppController.prototype, "getHello", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)("health"),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Object)
|
||||
], AppController.prototype, "getHealth", null);
|
||||
exports.AppController = AppController = __decorate([
|
||||
(0, common_1.Controller)(),
|
||||
__metadata("design:paramtypes", [app_service_1.AppService])
|
||||
], AppController);
|
||||
//# sourceMappingURL=app.controller.js.map
|
||||
1
apps/api/src/app.controller.js.map
Normal file
1
apps/api/src/app.controller.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,+CAA2C;AASpC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACK;IAA7B,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAGvD,QAAQ;QACN,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACpC,CAAC;IAGD,SAAS;QACP,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,MAAM,EAAE,SAAS;gBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AAlBY,sCAAa;AAIxB;IADC,IAAA,YAAG,GAAE;;;;6CAGL;AAGD;IADC,IAAA,YAAG,EAAC,QAAQ,CAAC;;;;8CASb;wBAjBU,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAE8B,wBAAU;GADxC,aAAa,CAkBzB"}
|
||||
31
apps/api/src/app.controller.test.ts
Normal file
31
apps/api/src/app.controller.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AppService } from "./app.service";
|
||||
import { AppController } from "./app.controller";
|
||||
|
||||
describe("AppController", () => {
|
||||
const appService = new AppService();
|
||||
const controller = new AppController(appService);
|
||||
|
||||
describe("getHello", () => {
|
||||
it('should return "Mosaic Stack API"', () => {
|
||||
expect(controller.getHello()).toBe("Mosaic Stack API");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHealth", () => {
|
||||
it("should return health status", () => {
|
||||
const result = controller.getHealth();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.status).toBe("healthy");
|
||||
expect(result.data.timestamp).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("AppService", () => {
|
||||
const service = new AppService();
|
||||
|
||||
it('should return "Mosaic Stack API"', () => {
|
||||
expect(service.getHello()).toBe("Mosaic Stack API");
|
||||
});
|
||||
});
|
||||
29
apps/api/src/app.controller.ts
Normal file
29
apps/api/src/app.controller.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
import { AppService } from "./app.service";
|
||||
import type { ApiResponse } from "@mosaic/shared";
|
||||
|
||||
interface HealthStatus {
|
||||
status: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
|
||||
@Get("health")
|
||||
getHealth(): ApiResponse<HealthStatus> {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
status: "healthy",
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
3
apps/api/src/app.module.d.ts
vendored
Normal file
3
apps/api/src/app.module.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export declare class AppModule {
|
||||
}
|
||||
//# sourceMappingURL=app.module.d.ts.map
|
||||
1
apps/api/src/app.module.d.ts.map
Normal file
1
apps/api/src/app.module.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.module.d.ts","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":"AAIA,qBAKa,SAAS;CAAG"}
|
||||
23
apps/api/src/app.module.js
Normal file
23
apps/api/src/app.module.js
Normal file
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AppModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const app_controller_1 = require("./app.controller");
|
||||
const app_service_1 = require("./app.service");
|
||||
let AppModule = class AppModule {
|
||||
};
|
||||
exports.AppModule = AppModule;
|
||||
exports.AppModule = AppModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
imports: [],
|
||||
controllers: [app_controller_1.AppController],
|
||||
providers: [app_service_1.AppService],
|
||||
})
|
||||
], AppModule);
|
||||
//# sourceMappingURL=app.module.js.map
|
||||
1
apps/api/src/app.module.js.map
Normal file
1
apps/api/src/app.module.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AACjD,+CAA2C;AAOpC,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IALrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,SAAS,EAAE,CAAC,wBAAU,CAAC;KACxB,CAAC;GACW,SAAS,CAAG"}
|
||||
10
apps/api/src/app.module.ts
Normal file
10
apps/api/src/app.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AppController } from "./app.controller";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
4
apps/api/src/app.service.d.ts
vendored
Normal file
4
apps/api/src/app.service.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export declare class AppService {
|
||||
getHello(): string;
|
||||
}
|
||||
//# sourceMappingURL=app.service.d.ts.map
|
||||
1
apps/api/src/app.service.d.ts.map
Normal file
1
apps/api/src/app.service.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.service.d.ts","sourceRoot":"","sources":["app.service.ts"],"names":[],"mappings":"AAEA,qBACa,UAAU;IACrB,QAAQ,IAAI,MAAM;CAGnB"}
|
||||
20
apps/api/src/app.service.js
Normal file
20
apps/api/src/app.service.js
Normal file
@@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AppService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
let AppService = class AppService {
|
||||
getHello() {
|
||||
return "Mosaic Stack API";
|
||||
}
|
||||
};
|
||||
exports.AppService = AppService;
|
||||
exports.AppService = AppService = __decorate([
|
||||
(0, common_1.Injectable)()
|
||||
], AppService);
|
||||
//# sourceMappingURL=app.service.js.map
|
||||
1
apps/api/src/app.service.js.map
Normal file
1
apps/api/src/app.service.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.service.js","sourceRoot":"","sources":["app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,QAAQ;QACN,OAAO,kBAAkB,CAAC;IAC5B,CAAC;CACF,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"}
|
||||
8
apps/api/src/app.service.ts
Normal file
8
apps/api/src/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return "Mosaic Stack API";
|
||||
}
|
||||
}
|
||||
2
apps/api/src/main.d.ts
vendored
Normal file
2
apps/api/src/main.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=main.d.ts.map
|
||||
1
apps/api/src/main.d.ts.map
Normal file
1
apps/api/src/main.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":""}
|
||||
16
apps/api/src/main.js
Normal file
16
apps/api/src/main.js
Normal file
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core_1 = require("@nestjs/core");
|
||||
const app_module_1 = require("./app.module");
|
||||
async function bootstrap() {
|
||||
const app = await core_1.NestFactory.create(app_module_1.AppModule);
|
||||
app.enableCors();
|
||||
const port = process.env["PORT"] ?? 3001;
|
||||
await app.listen(port);
|
||||
console.log(`API running on http://localhost:${port}`);
|
||||
}
|
||||
bootstrap().catch((err) => {
|
||||
console.error("Failed to start application:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
//# sourceMappingURL=main.js.map
|
||||
1
apps/api/src/main.js.map
Normal file
1
apps/api/src/main.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAChD,GAAG,CAAC,UAAU,EAAE,CAAC;IAEjB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IACzC,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACjC,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
||||
17
apps/api/src/main.ts
Normal file
17
apps/api/src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors();
|
||||
|
||||
const port = process.env["PORT"] ?? 3001;
|
||||
await app.listen(port);
|
||||
|
||||
console.log(`API running on http://localhost:${port}`);
|
||||
}
|
||||
|
||||
bootstrap().catch((err: unknown) => {
|
||||
console.error("Failed to start application:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
13
apps/api/tsconfig.json
Normal file
13
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@mosaic/config/typescript/nestjs",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
27
apps/api/vitest.config.ts
Normal file
27
apps/api/vitest.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import swc from "unplugin-swc";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: false,
|
||||
environment: "node",
|
||||
include: ["src/**/*.test.ts", "src/**/*.spec.ts"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "json", "html"],
|
||||
exclude: ["node_modules/", "dist/"],
|
||||
},
|
||||
setupFiles: ["./vitest.setup.ts"],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
swc.vite({
|
||||
module: { type: "es6" },
|
||||
}),
|
||||
],
|
||||
});
|
||||
1
apps/api/vitest.setup.ts
Normal file
1
apps/api/vitest.setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import "reflect-metadata";
|
||||
6
apps/web/next-env.d.ts
vendored
Normal file
6
apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
7
apps/web/next.config.ts
Normal file
7
apps/web/next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: ["@mosaic/ui", "@mosaic/shared"],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
36
apps/web/package.json
Normal file
36
apps/web/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@mosaic/web",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev --turbopack --port 3000",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rm -rf .next",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mosaic/shared": "workspace:*",
|
||||
"@mosaic/ui": "workspace:*",
|
||||
"next": "^16.1.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mosaic/config": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"jsdom": "^26.0.0",
|
||||
"typescript": "^5.8.2",
|
||||
"vitest": "^3.0.8"
|
||||
}
|
||||
}
|
||||
20
apps/web/src/app/globals.css
Normal file
20
apps/web/src/app/globals.css
Normal file
@@ -0,0 +1,20 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-rgb: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: rgb(var(--background-rgb));
|
||||
}
|
||||
16
apps/web/src/app/layout.tsx
Normal file
16
apps/web/src/app/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Mosaic Stack",
|
||||
description: "Mosaic Stack Web Application",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
22
apps/web/src/app/page.test.tsx
Normal file
22
apps/web/src/app/page.test.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it, afterEach } from "vitest";
|
||||
import { render, screen, cleanup } from "@testing-library/react";
|
||||
import Home from "./page";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("Home", () => {
|
||||
it("should render the title", () => {
|
||||
render(<Home />);
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Mosaic Stack");
|
||||
});
|
||||
|
||||
it("should render the buttons", () => {
|
||||
render(<Home />);
|
||||
const buttons = screen.getAllByRole("button");
|
||||
expect(buttons.length).toBe(2);
|
||||
expect(buttons[0]).toHaveTextContent("Get Started");
|
||||
expect(buttons[1]).toHaveTextContent("Learn More");
|
||||
});
|
||||
});
|
||||
16
apps/web/src/app/page.tsx
Normal file
16
apps/web/src/app/page.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Button } from "@mosaic/ui";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center p-24">
|
||||
<h1 className="text-4xl font-bold mb-8">Mosaic Stack</h1>
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
Welcome to the Mosaic Stack monorepo
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<Button variant="primary">Get Started</Button>
|
||||
<Button variant="secondary">Learn More</Button>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
11
apps/web/tsconfig.json
Normal file
11
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@mosaic/config/typescript/nextjs",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
23
apps/web/vitest.config.ts
Normal file
23
apps/web/vitest.config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import path from "path";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: false,
|
||||
environment: "jsdom",
|
||||
include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "json", "html"],
|
||||
exclude: ["node_modules/", ".next/"],
|
||||
},
|
||||
setupFiles: ["./vitest.setup.ts"],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
1
apps/web/vitest.setup.ts
Normal file
1
apps/web/vitest.setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
Reference in New Issue
Block a user