@quanticjs/create-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/deps.d.ts +4 -0
  2. package/dist/deps.js +97 -0
  3. package/dist/deps.js.map +1 -0
  4. package/dist/generators/backend.d.ts +2 -0
  5. package/dist/generators/backend.js +20 -0
  6. package/dist/generators/backend.js.map +1 -0
  7. package/dist/generators/bff.d.ts +2 -0
  8. package/dist/generators/bff.js +9 -0
  9. package/dist/generators/bff.js.map +1 -0
  10. package/dist/generators/claude.d.ts +2 -0
  11. package/dist/generators/claude.js +45 -0
  12. package/dist/generators/claude.js.map +1 -0
  13. package/dist/generators/docker.d.ts +2 -0
  14. package/dist/generators/docker.js +10 -0
  15. package/dist/generators/docker.js.map +1 -0
  16. package/dist/generators/e2e.d.ts +2 -0
  17. package/dist/generators/e2e.js +8 -0
  18. package/dist/generators/e2e.js.map +1 -0
  19. package/dist/generators/frontend.d.ts +2 -0
  20. package/dist/generators/frontend.js +28 -0
  21. package/dist/generators/frontend.js.map +1 -0
  22. package/dist/generators/module.d.ts +2 -0
  23. package/dist/generators/module.js +35 -0
  24. package/dist/generators/module.js.map +1 -0
  25. package/dist/generators/root.d.ts +2 -0
  26. package/dist/generators/root.js +7 -0
  27. package/dist/generators/root.js.map +1 -0
  28. package/dist/generators/scripts.d.ts +2 -0
  29. package/dist/generators/scripts.js +10 -0
  30. package/dist/generators/scripts.js.map +1 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +40 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/prompts.d.ts +8 -0
  35. package/dist/prompts.js +53 -0
  36. package/dist/prompts.js.map +1 -0
  37. package/dist/scaffold.d.ts +2 -0
  38. package/dist/scaffold.js +79 -0
  39. package/dist/scaffold.js.map +1 -0
  40. package/dist/utils/exec.d.ts +2 -0
  41. package/dist/utils/exec.js +14 -0
  42. package/dist/utils/exec.js.map +1 -0
  43. package/dist/utils/template.d.ts +10 -0
  44. package/dist/utils/template.js +20 -0
  45. package/dist/utils/template.js.map +1 -0
  46. package/dist/utils/validate.d.ts +4 -0
  47. package/dist/utils/validate.js +27 -0
  48. package/dist/utils/validate.js.map +1 -0
  49. package/package.json +50 -0
  50. package/templates/backend/app.module.ts.ejs +61 -0
  51. package/templates/backend/bff.controller.ts.ejs +60 -0
  52. package/templates/backend/bff.module.ts.ejs +10 -0
  53. package/templates/backend/bff.service.ts.ejs +6 -0
  54. package/templates/backend/create-schema.migration.ts.ejs +15 -0
  55. package/templates/backend/data-source.ts.ejs +13 -0
  56. package/templates/backend/env.example.ejs +30 -0
  57. package/templates/backend/main.ts.ejs +43 -0
  58. package/templates/backend/module.ts.ejs +14 -0
  59. package/templates/backend/nest-cli.json.ejs +8 -0
  60. package/templates/backend/package.json.ejs +23 -0
  61. package/templates/backend/tsconfig.build.json.ejs +4 -0
  62. package/templates/backend/tsconfig.json.ejs +24 -0
  63. package/templates/claude/CLAUDE.md.ejs +86 -0
  64. package/templates/claude/hooks/auto-format.sh +22 -0
  65. package/templates/claude/hooks/check-secrets.sh +49 -0
  66. package/templates/claude/hooks/guard-destructive.sh +42 -0
  67. package/templates/claude/hooks/on-compaction.sh +29 -0
  68. package/templates/claude/mcp.json +10 -0
  69. package/templates/claude/rules/api-patterns.md +86 -0
  70. package/templates/claude/rules/auth-patterns.md +109 -0
  71. package/templates/claude/rules/backend-patterns.md +421 -0
  72. package/templates/claude/rules/database-patterns.md +96 -0
  73. package/templates/claude/rules/docker-patterns.md +86 -0
  74. package/templates/claude/rules/frontend-patterns.md +262 -0
  75. package/templates/claude/rules/observability-backend.md +132 -0
  76. package/templates/claude/rules/observability-frontend.md +49 -0
  77. package/templates/claude/rules/playwright-mcp.md +80 -0
  78. package/templates/claude/rules/resilience-ops.md +103 -0
  79. package/templates/claude/rules/testing-e2e-ui.md +190 -0
  80. package/templates/claude/rules/testing-patterns.md +94 -0
  81. package/templates/claude/rules/workflow-backend.md +64 -0
  82. package/templates/claude/rules/workflow-frontend.md +60 -0
  83. package/templates/claude/settings.json +68 -0
  84. package/templates/claude/skills/add-api-endpoint/SKILL.md +59 -0
  85. package/templates/claude/skills/add-auth-endpoint/SKILL.md +68 -0
  86. package/templates/claude/skills/add-entity/SKILL.md +56 -0
  87. package/templates/claude/skills/add-event/SKILL.md +127 -0
  88. package/templates/claude/skills/add-feature/SKILL.md +20 -0
  89. package/templates/claude/skills/add-frontend-page/SKILL.md +75 -0
  90. package/templates/claude/skills/add-handler/SKILL.md +105 -0
  91. package/templates/claude/skills/add-integration/SKILL.md +176 -0
  92. package/templates/claude/skills/add-migration/SKILL.md +20 -0
  93. package/templates/claude/skills/add-module/SKILL.md +89 -0
  94. package/templates/claude/skills/add-realtime/SKILL.md +119 -0
  95. package/templates/claude/skills/audit-rules/SKILL.md +120 -0
  96. package/templates/claude/skills/debugging/SKILL.md +105 -0
  97. package/templates/claude/skills/docker-dev/SKILL.md +86 -0
  98. package/templates/claude/skills/e2e-audit/SKILL.md +85 -0
  99. package/templates/claude/skills/e2e-full/SKILL.md +132 -0
  100. package/templates/claude/skills/e2e-scan/SKILL.md +171 -0
  101. package/templates/claude/skills/e2e-verify/SKILL.md +145 -0
  102. package/templates/claude/skills/fix-bug/SKILL.md +33 -0
  103. package/templates/claude/skills/implement-spec/SKILL.md +98 -0
  104. package/templates/claude/skills/review-code/SKILL.md +109 -0
  105. package/templates/claude/skills/review-spec/SKILL.md +216 -0
  106. package/templates/claude/skills/run-tests/SKILL.md +37 -0
  107. package/templates/claude/skills/specify/SKILL.md +87 -0
  108. package/templates/claude/skills/write-backend-tests/SKILL.md +182 -0
  109. package/templates/claude/skills/write-ui-tests/SKILL.md +118 -0
  110. package/templates/docker/Dockerfile.client.ejs +14 -0
  111. package/templates/docker/Dockerfile.ejs +28 -0
  112. package/templates/docker/docker-compose.test.yml.ejs +54 -0
  113. package/templates/docker/docker-compose.yml.ejs +76 -0
  114. package/templates/docker/nginx.conf.ejs +21 -0
  115. package/templates/frontend/App.tsx.ejs +64 -0
  116. package/templates/frontend/DashboardPage.tsx.ejs +37 -0
  117. package/templates/frontend/LoginPage.tsx.ejs +20 -0
  118. package/templates/frontend/NotFoundPage.tsx.ejs +15 -0
  119. package/templates/frontend/api-client.ts.ejs +15 -0
  120. package/templates/frontend/index.css.ejs +57 -0
  121. package/templates/frontend/index.html.ejs +13 -0
  122. package/templates/frontend/main.tsx.ejs +10 -0
  123. package/templates/frontend/package.json.ejs +16 -0
  124. package/templates/frontend/playwright.config.ts.ejs +20 -0
  125. package/templates/frontend/postcss.config.js.ejs +3 -0
  126. package/templates/frontend/smoke.spec.ts.ejs +37 -0
  127. package/templates/frontend/tailwind.config.ts.ejs +56 -0
  128. package/templates/frontend/tsconfig.json.ejs +25 -0
  129. package/templates/frontend/tsconfig.node.json.ejs +15 -0
  130. package/templates/frontend/utils.ts.ejs +6 -0
  131. package/templates/frontend/vite-env.d.ts.ejs +1 -0
  132. package/templates/frontend/vite.config.ts.ejs +20 -0
  133. package/templates/root/gitignore.ejs +9 -0
  134. package/templates/root/prettierrc.ejs +7 -0
  135. package/templates/scripts/init-db.sh.ejs +8 -0
  136. package/templates/scripts/save-auth-state.ts.ejs +24 -0
@@ -0,0 +1,10 @@
1
+ export interface TemplateContext {
2
+ projectName: string;
3
+ projectDescription: string;
4
+ projectNameUnderscored: string;
5
+ projectNamePascal: string;
6
+ modules: string[];
7
+ }
8
+ export declare function renderTemplate(templatePath: string, ctx: TemplateContext): string;
9
+ export declare function writeFile(filePath: string, content: string): void;
10
+ export declare function renderAndWrite(templatePath: string, outputPath: string, ctx: TemplateContext): void;
@@ -0,0 +1,20 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import ejs from 'ejs';
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const TEMPLATES_DIR = join(__dirname, '..', '..', 'templates');
7
+ export function renderTemplate(templatePath, ctx) {
8
+ const fullPath = join(TEMPLATES_DIR, templatePath);
9
+ const content = readFileSync(fullPath, 'utf-8');
10
+ return ejs.render(content, ctx);
11
+ }
12
+ export function writeFile(filePath, content) {
13
+ mkdirSync(dirname(filePath), { recursive: true });
14
+ writeFileSync(filePath, content, 'utf-8');
15
+ }
16
+ export function renderAndWrite(templatePath, outputPath, ctx) {
17
+ const content = renderTemplate(templatePath, ctx);
18
+ writeFile(outputPath, content);
19
+ }
20
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAU/D,MAAM,UAAU,cAAc,CAAC,YAAoB,EAAE,GAAoB;IACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,OAAe;IACzD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,YAAoB,EACpB,UAAkB,EAClB,GAAoB;IAEpB,MAAM,OAAO,GAAG,cAAc,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAClD,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function validateProjectName(name: string): string | true;
2
+ export declare function validateModuleName(name: string): string | true;
3
+ export declare function toUnderscored(kebab: string): string;
4
+ export declare function toPascalCase(kebab: string): string;
@@ -0,0 +1,27 @@
1
+ const KEBAB_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
2
+ export function validateProjectName(name) {
3
+ if (!name)
4
+ return 'Project name is required';
5
+ if (!KEBAB_RE.test(name))
6
+ return 'Must be kebab-case (e.g. cr-workflow)';
7
+ if (name.length > 50)
8
+ return 'Must be 50 characters or fewer';
9
+ return true;
10
+ }
11
+ export function validateModuleName(name) {
12
+ if (!name)
13
+ return 'Module name is required';
14
+ if (!KEBAB_RE.test(name))
15
+ return 'Must be kebab-case (e.g. change-request)';
16
+ return true;
17
+ }
18
+ export function toUnderscored(kebab) {
19
+ return kebab.replace(/-/g, '_');
20
+ }
21
+ export function toPascalCase(kebab) {
22
+ return kebab
23
+ .split('-')
24
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
25
+ .join('');
26
+ }
27
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AAEjD,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,CAAC,IAAI;QAAE,OAAO,0BAA0B,CAAC;IAC7C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,uCAAuC,CAAC;IACzE,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,gCAAgC,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC,IAAI;QAAE,OAAO,yBAAyB,CAAC;IAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,0CAA0C,CAAC;IAC5E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@quanticjs/create-app",
3
+ "version": "0.1.1",
4
+ "description": "Scaffold a QuanticJS project — NestJS modular monolith + React frontend",
5
+ "bin": {
6
+ "create-quanticjs-app": "./dist/index.js"
7
+ },
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "templates"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "start": "node dist/index.js",
18
+ "lint": "eslint src --ext .ts",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "quanticjs",
23
+ "nestjs",
24
+ "react",
25
+ "scaffold",
26
+ "cli",
27
+ "cqrs"
28
+ ],
29
+ "author": "QuanticJS",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "chalk": "^5.3.0",
33
+ "commander": "^12.1.0",
34
+ "ejs": "^3.1.10",
35
+ "inquirer": "^9.3.0",
36
+ "ora": "^8.1.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/ejs": "^3.1.5",
40
+ "@types/inquirer": "^9.0.7",
41
+ "@types/node": "^20.14.0",
42
+ "typescript": "^5.5.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=20.0.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }
@@ -0,0 +1,61 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { ConfigModule, ConfigService } from '@nestjs/config';
3
+ import { TypeOrmModule } from '@nestjs/typeorm';
4
+ import { CqrsModule } from '@nestjs/cqrs';
5
+ import { ScheduleModule } from '@nestjs/schedule';
6
+ import { LoggerModule } from 'nestjs-pino';
7
+ import { QuanticModule } from '@nestjs-cqrs/quanticjs';
8
+ import { QuanticHealthModule } from '@quanticjs/core';
9
+ import { BffModule } from './bff/bff.module';
10
+ <% modules.forEach(function(mod) { -%>
11
+ import { <%= toPascalCase(mod) %>Module } from './<%= mod %>/<%= mod %>.module';
12
+ <% }); -%>
13
+
14
+ @Module({
15
+ imports: [
16
+ ConfigModule.forRoot({ isGlobal: true }),
17
+
18
+ LoggerModule.forRoot({
19
+ pinoHttp: {
20
+ transport:
21
+ process.env.NODE_ENV !== 'production'
22
+ ? { target: 'pino-pretty', options: { colorize: true } }
23
+ : undefined,
24
+ serializers: {
25
+ req: (req: any) => ({ id: req.id, method: req.method, url: req.url }),
26
+ res: (res: any) => ({ statusCode: res.statusCode }),
27
+ },
28
+ },
29
+ }),
30
+
31
+ TypeOrmModule.forRootAsync({
32
+ imports: [ConfigModule],
33
+ inject: [ConfigService],
34
+ useFactory: (config: ConfigService) => ({
35
+ type: 'postgres',
36
+ host: config.get('DATABASE_HOST', 'localhost'),
37
+ port: config.get<number>('DATABASE_PORT', 5432),
38
+ username: config.get('DATABASE_USER', 'postgres'),
39
+ password: config.get('DATABASE_PASSWORD', 'postgres'),
40
+ database: config.get('DATABASE_NAME', '<%= projectNameUnderscored %>'),
41
+ autoLoadEntities: true,
42
+ synchronize: false,
43
+ logging: config.get('NODE_ENV') !== 'production',
44
+ }),
45
+ }),
46
+
47
+ QuanticModule.forRoot({
48
+ redis: { url: process.env.REDIS_URL ?? 'redis://localhost:6379' },
49
+ }),
50
+
51
+ QuanticHealthModule.forRoot(),
52
+ ScheduleModule.forRoot(),
53
+ CqrsModule.forRoot(),
54
+
55
+ BffModule,
56
+ <% modules.forEach(function(mod) { -%>
57
+ <%= toPascalCase(mod) %>Module,
58
+ <% }); -%>
59
+ ],
60
+ })
61
+ export class AppModule {}
@@ -0,0 +1,60 @@
1
+ import { Controller, Get, Post, Req, Res, Query } from '@nestjs/common';
2
+ import { ApiTags, ApiOperation } from '@nestjs/swagger';
3
+ import { Request, Response } from 'express';
4
+ import { Public } from '@nestjs-cqrs/quanticjs';
5
+ import { BffService } from './bff.service';
6
+
7
+ @ApiTags('auth')
8
+ @Controller('auth')
9
+ export class BffController {
10
+ constructor(private readonly bffService: BffService) {}
11
+
12
+ @Public()
13
+ @Get('login')
14
+ @ApiOperation({ summary: 'Redirect to Keycloak login' })
15
+ login(
16
+ @Query('provider') provider: string,
17
+ @Query('returnTo') returnTo: string,
18
+ @Res() res: Response,
19
+ ) {
20
+ // TODO: implement PKCE + Keycloak redirect
21
+ res.redirect('/');
22
+ }
23
+
24
+ @Public()
25
+ @Get('callback')
26
+ @ApiOperation({ summary: 'OIDC callback — exchange code for tokens' })
27
+ async callback(@Req() req: Request, @Res() res: Response) {
28
+ // TODO: exchange code, store tokens in Redis, set httpOnly cookie
29
+ res.redirect('/');
30
+ }
31
+
32
+ @Post('refresh')
33
+ @ApiOperation({ summary: 'Refresh access token' })
34
+ async refresh(@Req() req: Request) {
35
+ // TODO: refresh via Redis-stored refresh token
36
+ return { success: true };
37
+ }
38
+
39
+ @Post('logout')
40
+ @ApiOperation({ summary: 'Logout — clear session' })
41
+ async logout(@Req() req: Request, @Res() res: Response) {
42
+ // TODO: clear cookie, revoke Keycloak session, invalidate Redis
43
+ res.clearCookie('sid').json({ success: true });
44
+ }
45
+
46
+ @Get('me')
47
+ @ApiOperation({ summary: 'Get current user info' })
48
+ async me(@Req() req: Request) {
49
+ // TODO: read session from Redis, return user info
50
+ return {
51
+ id: '',
52
+ keycloakId: '',
53
+ email: '',
54
+ displayName: '',
55
+ role: 'user',
56
+ roles: ['user'],
57
+ permissions: [],
58
+ };
59
+ }
60
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { BffController } from './bff.controller';
3
+ import { BffService } from './bff.service';
4
+
5
+ @Module({
6
+ controllers: [BffController],
7
+ providers: [BffService],
8
+ exports: [BffService],
9
+ })
10
+ export class BffModule {}
@@ -0,0 +1,6 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ @Injectable()
4
+ export class BffService {
5
+ // TODO: implement session management (Redis), token exchange, PKCE
6
+ }
@@ -0,0 +1,15 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ export class CreateSchemas<%= timestamp %> implements MigrationInterface {
4
+ public async up(queryRunner: QueryRunner): Promise<void> {
5
+ <% modules.forEach(function(mod) { -%>
6
+ await queryRunner.query(`CREATE SCHEMA IF NOT EXISTS <%= mod.replace(/-/g, '_') %>`);
7
+ <% }); -%>
8
+ }
9
+
10
+ public async down(queryRunner: QueryRunner): Promise<void> {
11
+ <% modules.slice().reverse().forEach(function(mod) { -%>
12
+ await queryRunner.query(`DROP SCHEMA IF EXISTS <%= mod.replace(/-/g, '_') %> CASCADE`);
13
+ <% }); -%>
14
+ }
15
+ }
@@ -0,0 +1,13 @@
1
+ import { DataSource } from 'typeorm';
2
+
3
+ export default new DataSource({
4
+ type: 'postgres',
5
+ host: process.env.DATABASE_HOST ?? 'localhost',
6
+ port: parseInt(process.env.DATABASE_PORT ?? '5432', 10),
7
+ username: process.env.DATABASE_USER ?? 'postgres',
8
+ password: process.env.DATABASE_PASSWORD ?? 'postgres',
9
+ database: process.env.DATABASE_NAME ?? '<%= projectNameUnderscored %>',
10
+ entities: ['src/**/entities/*.ts'],
11
+ migrations: ['src/migrations/*.ts'],
12
+ synchronize: false,
13
+ });
@@ -0,0 +1,30 @@
1
+ # Application
2
+ NODE_ENV=development
3
+ PORT=3000
4
+
5
+ # Database
6
+ DATABASE_HOST=postgres
7
+ DATABASE_PORT=5432
8
+ DATABASE_USER=postgres
9
+ DATABASE_PASSWORD=postgres
10
+ DATABASE_NAME=<%= projectNameUnderscored %>
11
+
12
+ # Redis
13
+ REDIS_URL=redis://redis:6379
14
+
15
+ # Keycloak
16
+ KEYCLOAK_URL=http://keycloak:8080
17
+ KEYCLOAK_REALM=<%= projectName %>
18
+ KEYCLOAK_CLIENT_ID=<%= projectName %>-backend
19
+ KEYCLOAK_CLIENT_SECRET=change-me
20
+
21
+ # Session
22
+ SESSION_SECRET=change-me-in-production
23
+ SESSION_COOKIE_NAME=sid
24
+
25
+ # Sentry (optional)
26
+ SENTRY_DSN=
27
+
28
+ # Unleash (optional — features enabled by default if not set)
29
+ UNLEASH_URL=
30
+ UNLEASH_API_KEY=
@@ -0,0 +1,43 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { ValidationPipe } from '@nestjs/common';
3
+ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
4
+ import { Logger } from 'nestjs-pino';
5
+ import * as cookieParser from 'cookie-parser';
6
+ import { GlobalExceptionFilter, ResultInterceptor } from '@nestjs-cqrs/quanticjs';
7
+ import { AppModule } from './app.module';
8
+
9
+ async function bootstrap() {
10
+ const app = await NestFactory.create(AppModule, { bufferLogs: true });
11
+
12
+ app.useLogger(app.get(Logger));
13
+ app.use(cookieParser());
14
+ app.setGlobalPrefix('api', { exclude: ['auth/(.*)'] });
15
+
16
+ app.useGlobalFilters(app.get(GlobalExceptionFilter));
17
+ app.useGlobalInterceptors(app.get(ResultInterceptor));
18
+
19
+ app.useGlobalPipes(
20
+ new ValidationPipe({
21
+ whitelist: true,
22
+ forbidNonWhitelisted: true,
23
+ transform: true,
24
+ }),
25
+ );
26
+
27
+ if (process.env.NODE_ENV !== 'production') {
28
+ const config = new DocumentBuilder()
29
+ .setTitle('<%= projectNamePascal %>')
30
+ .setDescription('API documentation')
31
+ .setVersion('1.0')
32
+ .addBearerAuth()
33
+ .build();
34
+ const document = SwaggerModule.createDocument(app, config);
35
+ SwaggerModule.setup('api/docs', app, document);
36
+ }
37
+
38
+ app.enableShutdownHooks();
39
+
40
+ const port = process.env.PORT ?? 3000;
41
+ await app.listen(port);
42
+ }
43
+ bootstrap();
@@ -0,0 +1,14 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { CqrsModule } from '@nestjs/cqrs';
3
+ import { TypeOrmModule } from '@nestjs/typeorm';
4
+
5
+ const CommandHandlers: never[] = [];
6
+ const QueryHandlers: never[] = [];
7
+ const Validators: never[] = [];
8
+
9
+ @Module({
10
+ imports: [CqrsModule, TypeOrmModule.forFeature([])],
11
+ controllers: [],
12
+ providers: [...CommandHandlers, ...QueryHandlers, ...Validators],
13
+ })
14
+ export class <%= modulePascal %>Module {}
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "<%= projectName %>",
3
+ "version": "0.0.1",
4
+ "description": "<%= projectDescription %>",
5
+ "private": true,
6
+ "scripts": {
7
+ "build": "nest build",
8
+ "start": "nest start",
9
+ "start:dev": "nest start --watch",
10
+ "start:debug": "nest start --debug --watch",
11
+ "start:prod": "node dist/main",
12
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
13
+ "format": "prettier --write \"src/**/*.ts\"",
14
+ "test": "jest",
15
+ "test:watch": "jest --watch",
16
+ "test:cov": "jest --coverage",
17
+ "test:e2e": "jest --config ./test/jest-e2e.json",
18
+ "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js -d src/data-source.ts",
19
+ "migration:generate": "npm run typeorm -- migration:generate",
20
+ "migration:run": "npm run typeorm -- migration:run",
21
+ "migration:revert": "npm run typeorm -- migration:revert"
22
+ }
23
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": true,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "target": "ES2021",
10
+ "sourceMap": true,
11
+ "outDir": "./dist",
12
+ "baseUrl": "./",
13
+ "incremental": true,
14
+ "skipLibCheck": true,
15
+ "strict": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noImplicitOverride": true,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "paths": {
21
+ "@/*": ["src/*"]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,86 @@
1
+ # <%= projectNamePascal %>
2
+
3
+ ## Stack
4
+
5
+ - **Backend:** NestJS (modular monolith), CQRS with pipeline behaviors, TypeORM, PostgreSQL, Redis, Keycloak BFF auth
6
+ - **Frontend:** React, Vite, TanStack Query, Zustand, React Hook Form + Zod, Tailwind CSS, shadcn/ui
7
+ - **Infrastructure:** Docker Compose (local dev), Kubernetes + Helm + ArgoCD (production)
8
+
9
+ ## Architecture
10
+
11
+ **Modular monolith.** One backend Docker image, one frontend Docker image. Modules communicate via CommandBus/QueryBus. Each module owns its own PostgreSQL schema.
12
+
13
+ **CQRS.** Every operation is a Command/Query class + Handler. Controllers are thin — they only parse requests and dispatch to the bus.
14
+
15
+ **BFF Authentication.** Tokens stored server-side in Redis. Browser gets httpOnly cookies only. No tokens in localStorage/sessionStorage ever.
16
+
17
+ ## Dev Workflow
18
+
19
+ ```bash
20
+ # Start backend + infrastructure
21
+ docker compose up
22
+
23
+ # Start frontend (separate terminal)
24
+ cd client && npm run dev
25
+
26
+ # Browser
27
+ open http://localhost:5173
28
+ ```
29
+
30
+ **Ports:** 5173 (frontend/Vite), 3000 (backend API), 8080 (Keycloak)
31
+
32
+ ## Key Conventions
33
+
34
+ - Database columns are **camelCase** (TypeORM default naming strategy)
35
+ - Handlers return `Result<T>` — never throw for business errors
36
+ - Validation: DTOs use class-validator, Commands use Zod via `@Validate`
37
+ - Entities extend `BaseEntity` from the shared kernel
38
+ - Generate migrations from entities: `npx typeorm migration:generate src/migrations/Name`
39
+
40
+ ## How to Build — Skill Routing
41
+
42
+ Use the appropriate skill for each task. Skills enforce architectural rules automatically.
43
+
44
+ | When you need to... | Use |
45
+ |---|---|
46
+ | Add a full feature (backend + frontend) | `/add-feature` (orchestrates all sub-skills) |
47
+ | Add a command/query + validator + handler | `/add-handler` |
48
+ | Add a TypeORM entity | `/add-entity` |
49
+ | Generate a database migration | `/add-migration` |
50
+ | Wire a handler to an HTTP endpoint | `/add-api-endpoint` |
51
+ | Add an authenticated/role-gated endpoint | `/add-auth-endpoint` |
52
+ | Create a new bounded context module | `/add-module` |
53
+ | Add a React page with data fetching | `/add-frontend-page` |
54
+ | Integrate an external API (AI, payment, etc.) | `/add-integration` |
55
+ | Add inter-module async events | `/add-event` |
56
+ | Add live WebSocket updates | `/add-realtime` |
57
+ | Write backend tests | `/write-backend-tests` |
58
+ | Write Playwright E2E tests | `/write-ui-tests` |
59
+ | Find missing E2E coverage | `/e2e-scan` |
60
+ | Walk through journeys via MCP browser | `/e2e-verify` |
61
+ | Audit specs against real pages via MCP | `/e2e-audit` |
62
+ | Run full E2E suite (scan + audit + verify) | `/e2e-full` |
63
+ | Run the test suite | `/run-tests` |
64
+ | Fix a bug (TDD workflow) | `/fix-bug` |
65
+ | Review code before merge | `/review-code` |
66
+ | Debug a failing service | `/debugging` |
67
+ | Manage Docker dev environment | `/docker-dev` |
68
+ | Write a feature spec | `/specify` |
69
+ | Review a spec against rules | `/review-spec` (validates structure + rules compliance, writes corrected `-v2.md`) |
70
+ | Implement an existing spec | `/implement-spec` (spec-driven, picks only needed sub-skills) |
71
+ | Audit code against ADR rules | `/audit-rules` |
72
+
73
+ **When in doubt, start with `/add-feature`** — it walks through the full flow. If a spec already exists in `docs/specs/`, use `/implement-spec` instead.
74
+
75
+ ## Test Commands
76
+
77
+ ```bash
78
+ # Backend unit + integration tests
79
+ npm test
80
+
81
+ # Frontend tests
82
+ cd client && npm test
83
+
84
+ # E2E tests
85
+ cd client && npx playwright test
86
+ ```
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # PreToolUse hook: auto-format staged files before commit.
3
+ # Exit 0 = allow (formatting is best-effort, never blocks commit).
4
+ set -uo pipefail
5
+
6
+ INPUT=$(cat)
7
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
8
+
9
+ # Only run on git commit
10
+ if ! echo "$COMMAND" | grep -qE "git commit" 2>/dev/null; then
11
+ exit 0
12
+ fi
13
+
14
+ # Format staged .ts/.tsx/.js/.jsx files
15
+ FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(tsx?|jsx?)$' || true)
16
+
17
+ if [[ -n "$FILES" ]]; then
18
+ echo "$FILES" | xargs npx prettier --write >/dev/null 2>&1 || true
19
+ echo "$FILES" | xargs git add 2>/dev/null || true
20
+ fi
21
+
22
+ exit 0
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # PreToolUse hook: prevent committing secrets.
3
+ # Exit 0 = allow, Exit 2 = block (stderr shown to Claude as feedback)
4
+ set -euo pipefail
5
+
6
+ INPUT=$(cat)
7
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
8
+
9
+ # Only check git add and git commit commands
10
+ if ! echo "$COMMAND" | grep -qE "git (add|commit)" 2>/dev/null; then
11
+ exit 0
12
+ fi
13
+
14
+ ERRORS=""
15
+
16
+ # Check for .env files being staged
17
+ if echo "$COMMAND" | grep -qE "git add" 2>/dev/null; then
18
+ if echo "$COMMAND" | grep -qE "\\.env" 2>/dev/null && ! echo "$COMMAND" | grep -qE "\\.env\\.example" 2>/dev/null; then
19
+ ERRORS="$ERRORS\n❌ .env file being staged — contains secrets. Add to .gitignore instead."
20
+ fi
21
+ fi
22
+
23
+ # Check staged files for secrets patterns
24
+ STAGED=$(git diff --cached --name-only 2>/dev/null)
25
+ if [[ -z "$STAGED" ]]; then
26
+ STAGED=$(git diff --name-only 2>/dev/null)
27
+ fi
28
+
29
+ for file in $STAGED; do
30
+ [[ -f "$file" ]] || continue
31
+ [[ "$file" == *.ts || "$file" == *.tsx || "$file" == *.js || "$file" == *.json || "$file" == *.yaml || "$file" == *.yml ]] || continue
32
+ [[ "$file" == *.spec.* || "$file" == *.test.* ]] && continue
33
+
34
+ if grep -qE "(API_KEY|SECRET_KEY|PRIVATE_KEY|PASSWORD)\s*=\s*['\"][^'\"]+['\"]" "$file" 2>/dev/null; then
35
+ ERRORS="$ERRORS\n❌ $file: Hardcoded secret detected. Use environment variables."
36
+ fi
37
+ done
38
+
39
+ # Check for private key files
40
+ if echo "$COMMAND" | grep -qE "\\.pem|\\.key|\\.p12" 2>/dev/null; then
41
+ ERRORS="$ERRORS\n❌ Private key file being staged. These must never be committed."
42
+ fi
43
+
44
+ if [[ -n "$ERRORS" ]]; then
45
+ echo -e "$ERRORS" >&2
46
+ exit 2
47
+ fi
48
+
49
+ exit 0
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # PreToolUse hook: block irreversible commands.
3
+ # Exit 0 = allow, Exit 2 = block (stderr shown to Claude as feedback)
4
+ set -euo pipefail
5
+
6
+ INPUT=$(cat)
7
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
8
+
9
+ if [[ -z "$COMMAND" ]]; then
10
+ exit 0
11
+ fi
12
+
13
+ # Force push to main/master
14
+ if echo "$COMMAND" | grep -qE "git push.*--force.*(main|master)" 2>/dev/null; then
15
+ echo "❌ BLOCKED: Force push to main/master. This rewrites shared history permanently." >&2
16
+ exit 2
17
+ fi
18
+
19
+ if echo "$COMMAND" | grep -qE "git push.*-f.*(main|master)" 2>/dev/null; then
20
+ echo "❌ BLOCKED: Force push to main/master. This rewrites shared history permanently." >&2
21
+ exit 2
22
+ fi
23
+
24
+ # git reset --hard
25
+ if echo "$COMMAND" | grep -qE "git reset --hard" 2>/dev/null; then
26
+ echo "❌ BLOCKED: git reset --hard discards uncommitted work permanently. Use git stash or create a backup branch first." >&2
27
+ exit 2
28
+ fi
29
+
30
+ # DROP TABLE / DROP DATABASE
31
+ if echo "$COMMAND" | grep -qiE "DROP (TABLE|DATABASE|SCHEMA)" 2>/dev/null; then
32
+ echo "❌ BLOCKED: DROP TABLE/DATABASE/SCHEMA is irreversible. Confirm with the user before proceeding." >&2
33
+ exit 2
34
+ fi
35
+
36
+ # rm -rf / or rm -rf .
37
+ if echo "$COMMAND" | grep -qE "rm -rf\s+(/|\.)\s*$" 2>/dev/null; then
38
+ echo "❌ BLOCKED: Catastrophic rm -rf target." >&2
39
+ exit 2
40
+ fi
41
+
42
+ exit 0