create-forgeon 0.1.38 → 0.2.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 (55) hide show
  1. package/README.md +12 -4
  2. package/package.json +1 -1
  3. package/src/cli/help.mjs +3 -2
  4. package/src/cli/options.mjs +142 -121
  5. package/src/cli/options.test.mjs +13 -10
  6. package/src/constants.mjs +11 -9
  7. package/src/core/docs.mjs +44 -23
  8. package/src/core/docs.test.mjs +21 -15
  9. package/src/core/scaffold.mjs +27 -15
  10. package/src/modules/db-prisma.mjs +134 -32
  11. package/src/modules/executor.test.mjs +44 -13
  12. package/src/modules/i18n.mjs +11 -28
  13. package/src/modules/logger.mjs +4 -1
  14. package/src/modules/swagger.mjs +7 -2
  15. package/src/presets/i18n.mjs +63 -40
  16. package/src/run-add-module.mjs +87 -17
  17. package/src/run-create-forgeon.mjs +33 -24
  18. package/templates/base/README.md +16 -3
  19. package/templates/base/apps/api/Dockerfile +6 -11
  20. package/templates/base/apps/api/package.json +13 -24
  21. package/templates/base/apps/api/src/app.module.ts +3 -5
  22. package/templates/base/apps/api/src/health/health.controller.ts +1 -19
  23. package/templates/base/apps/web/src/App.tsx +0 -5
  24. package/templates/base/docs/AI/MODULE_CHECKS.md +1 -1
  25. package/templates/base/docs/AI/ROADMAP.md +1 -1
  26. package/templates/base/infra/docker/.env.example +1 -6
  27. package/templates/base/infra/docker/compose.caddy.yml +13 -37
  28. package/templates/base/infra/docker/compose.nginx.yml +13 -37
  29. package/templates/base/infra/docker/compose.none.yml +8 -32
  30. package/templates/base/infra/docker/compose.yml +16 -40
  31. package/templates/base/package.json +12 -9
  32. package/templates/base/scripts/forgeon-sync-integrations.mjs +399 -0
  33. package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +0 -1
  34. package/templates/docs-fragments/AI_ARCHITECTURE/20b_env_db_prisma.md +1 -0
  35. package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +2 -2
  36. package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db_none.md +7 -0
  37. package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +0 -1
  38. package/templates/docs-fragments/AI_PROJECT/20b_structure_db_none.md +2 -0
  39. package/templates/docs-fragments/AI_PROJECT/20b_structure_db_prisma.md +1 -0
  40. package/templates/docs-fragments/README/10_stack.md +4 -4
  41. package/templates/docs-fragments/README/21_quick_start_dev_no_db.md +6 -0
  42. package/templates/module-presets/i18n/apps/web/src/App.tsx +0 -5
  43. /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/migrations/0001_init/migration.sql +0 -0
  44. /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/migrations/migration_lock.toml +0 -0
  45. /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/schema.prisma +0 -0
  46. /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/seed.ts +0 -0
  47. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/README.md +0 -0
  48. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/package.json +0 -0
  49. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma-config.loader.ts +0 -0
  50. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma-config.service.ts +0 -0
  51. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma-env.schema.ts +0 -0
  52. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma.module.ts +0 -0
  53. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/index.ts +0 -0
  54. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/prisma.service.ts +0 -0
  55. /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/tsconfig.json +0 -0
package/README.md CHANGED
@@ -10,9 +10,10 @@ CLI package for generating Forgeon fullstack monorepo projects.
10
10
 
11
11
  ## Usage
12
12
 
13
- ```bash
14
- npx create-forgeon@latest my-app --i18n true --proxy caddy
15
- ```
13
+ ```bash
14
+ npx create-forgeon@latest my-app --i18n true --db-prisma true --proxy caddy
15
+ npx create-forgeon@latest my-app --db-prisma false --proxy caddy
16
+ ```
16
17
 
17
18
  If flags are omitted, the CLI asks interactive questions.
18
19
  Project name stays text input; fixed-choice prompts use arrow-key selection (`Up/Down + Enter`).
@@ -22,11 +23,18 @@ npx create-forgeon@latest add --list
22
23
  npx create-forgeon@latest add i18n --project ./my-app
23
24
  npx create-forgeon@latest add jwt-auth --project ./my-app
24
25
  ```
26
+
27
+ ```bash
28
+ cd my-app
29
+ pnpm forgeon:sync-integrations
30
+ ```
25
31
 
26
32
  ## Notes
27
33
 
28
- - Canonical stack is fixed: NestJS + React + Prisma/Postgres + Docker.
34
+ - Canonical runtime stack is fixed: NestJS + React + Docker.
35
+ - DB is module-driven: `db-prisma` is default-on and can be disabled at scaffold time.
29
36
  - Reverse proxy options: `caddy` (default), `nginx`, `none`.
30
37
  - `add i18n` is implemented and applies backend/frontend i18n wiring.
31
38
  - `add jwt-auth` is implemented and auto-detects DB adapter support for refresh-token persistence.
39
+ - Integration sync is bundled by default and runs after `add` commands (best-effort).
32
40
  - Planned modules write docs notes under `docs/AI/MODULES/`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.1.38",
3
+ "version": "0.2.1",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
package/src/cli/help.mjs CHANGED
@@ -6,8 +6,9 @@ Usage:
6
6
  npx create-forgeon@latest add <module-id> [options]
7
7
  npx create-forgeon@latest add --list
8
8
 
9
- Create options:
10
- --i18n <true|false> Enable i18n (default: true)
9
+ Create options:
10
+ --db-prisma <true|false> Enable db-prisma module (default: true)
11
+ --i18n <true|false> Enable i18n (default: true)
11
12
  --proxy <caddy|nginx|none> Reverse proxy preset (default: caddy)
12
13
  --install Run pnpm install after generation
13
14
  -y, --yes Skip prompts and use defaults
@@ -1,121 +1,142 @@
1
- import readline from 'node:readline/promises';
2
- import { stdin as input, stdout as output } from 'node:process';
3
- import { DEFAULT_OPTIONS, DEFAULT_PROXY } from '../constants.mjs';
4
- import { promptSelect } from './prompt-select.mjs';
5
-
6
- const REMOVED_FLAGS = new Set(['frontend', 'db', 'docker']);
7
-
8
- export function parseCliArgs(argv) {
9
- const args = [...argv];
10
- const options = { ...DEFAULT_OPTIONS };
11
- const positional = [];
12
-
13
- for (let i = 0; i < args.length; i += 1) {
14
- const arg = args[i];
15
-
16
- if (arg === '--') continue;
17
-
18
- if (arg === '-h' || arg === '--help') {
19
- options.help = true;
20
- continue;
21
- }
22
-
23
- if (arg === '-y' || arg === '--yes') {
24
- options.yes = true;
25
- continue;
26
- }
27
-
28
- if (arg === '--install') {
29
- options.install = true;
30
- continue;
31
- }
32
-
33
- if (arg.startsWith('--no-')) {
34
- const key = arg.slice(5);
35
- if (REMOVED_FLAGS.has(key)) {
36
- throw new Error(`Option "--${key}" has been removed. Forgeon now uses a fixed stack.`);
37
- }
38
- if (key === 'install') options.install = false;
39
- if (key === 'i18n') options.i18n = false;
40
- continue;
41
- }
42
-
43
- if (arg.startsWith('--')) {
44
- const [keyRaw, inlineValue] = arg.split('=');
45
- const key = keyRaw.slice(2);
46
- if (REMOVED_FLAGS.has(key)) {
47
- throw new Error(`Option "--${key}" has been removed. Forgeon now uses a fixed stack.`);
48
- }
49
-
50
- let value = inlineValue;
51
- if (value === undefined && args[i + 1] && !args[i + 1].startsWith('-')) {
52
- value = args[i + 1];
53
- i += 1;
54
- }
55
-
56
- if (Object.prototype.hasOwnProperty.call(options, key)) {
57
- options[key] = value;
58
- }
59
-
60
- continue;
61
- }
62
-
63
- positional.push(arg);
64
- }
65
-
66
- return { options, positional };
67
- }
68
-
69
- export async function promptForMissingOptions(options) {
70
- const nextOptions = { ...options };
71
-
72
- if (!nextOptions.name) {
73
- if (!input.isTTY) {
74
- throw new Error('Project name is required in non-interactive mode. Pass it as first argument.');
75
- }
76
-
77
- const rl = readline.createInterface({ input, output });
78
- try {
79
- nextOptions.name = await rl.question('Project name: ');
80
- } finally {
81
- await rl.close();
82
- }
83
- }
84
-
85
- if (!nextOptions.yes && nextOptions.i18n === undefined) {
86
- nextOptions.i18n = await promptSelect({
87
- message: 'Enable i18n:',
88
- defaultValue: 'true',
89
- choices: [
90
- { label: 'true', value: 'true' },
91
- { label: 'false', value: 'false' },
92
- ],
93
- });
94
- }
95
-
96
- if (!nextOptions.yes && !nextOptions.proxy) {
97
- nextOptions.proxy = await promptSelect({
98
- message: 'Reverse proxy preset:',
99
- defaultValue: DEFAULT_PROXY,
100
- choices: [
101
- { label: 'caddy', value: 'caddy' },
102
- { label: 'nginx', value: 'nginx' },
103
- { label: 'none', value: 'none' },
104
- ],
105
- });
106
- }
107
-
108
- if (nextOptions.yes) {
109
- if (nextOptions.i18n === undefined) nextOptions.i18n = 'true';
110
- if (!nextOptions.proxy) nextOptions.proxy = DEFAULT_PROXY;
111
- }
112
-
113
- if (nextOptions.i18n === undefined) {
114
- nextOptions.i18n = 'true';
115
- }
116
- if (!nextOptions.proxy) {
117
- nextOptions.proxy = DEFAULT_PROXY;
118
- }
119
-
120
- return nextOptions;
121
- }
1
+ import readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import { DEFAULT_DB_PRISMA_ENABLED, DEFAULT_OPTIONS, DEFAULT_PROXY } from '../constants.mjs';
4
+ import { promptSelect } from './prompt-select.mjs';
5
+
6
+ const REMOVED_FLAGS = new Set(['frontend', 'db', 'docker']);
7
+
8
+ export function parseCliArgs(argv) {
9
+ const args = [...argv];
10
+ const options = { ...DEFAULT_OPTIONS };
11
+ const positional = [];
12
+
13
+ for (let i = 0; i < args.length; i += 1) {
14
+ const arg = args[i];
15
+
16
+ if (arg === '--') continue;
17
+
18
+ if (arg === '-h' || arg === '--help') {
19
+ options.help = true;
20
+ continue;
21
+ }
22
+
23
+ if (arg === '-y' || arg === '--yes') {
24
+ options.yes = true;
25
+ continue;
26
+ }
27
+
28
+ if (arg === '--install') {
29
+ options.install = true;
30
+ continue;
31
+ }
32
+
33
+ if (arg.startsWith('--no-')) {
34
+ const key = arg.slice(5);
35
+ if (REMOVED_FLAGS.has(key)) {
36
+ throw new Error(`Option "--${key}" has been removed. Forgeon now uses a fixed stack.`);
37
+ }
38
+ if (key === 'install') options.install = false;
39
+ if (key === 'i18n') options.i18n = false;
40
+ if (key === 'db-prisma') options.dbPrisma = false;
41
+ continue;
42
+ }
43
+
44
+ if (arg.startsWith('--')) {
45
+ const [keyRaw, inlineValue] = arg.split('=');
46
+ const key = keyRaw.slice(2);
47
+ if (REMOVED_FLAGS.has(key)) {
48
+ throw new Error(`Option "--${key}" has been removed. Forgeon now uses a fixed stack.`);
49
+ }
50
+
51
+ let value = inlineValue;
52
+ if (value === undefined && args[i + 1] && !args[i + 1].startsWith('-')) {
53
+ value = args[i + 1];
54
+ i += 1;
55
+ }
56
+
57
+ if (key === 'db-prisma') {
58
+ options.dbPrisma = value;
59
+ continue;
60
+ }
61
+
62
+ if (Object.prototype.hasOwnProperty.call(options, key)) {
63
+ options[key] = value;
64
+ }
65
+
66
+ continue;
67
+ }
68
+
69
+ positional.push(arg);
70
+ }
71
+
72
+ return { options, positional };
73
+ }
74
+
75
+ export async function promptForMissingOptions(options) {
76
+ const nextOptions = { ...options };
77
+
78
+ if (!nextOptions.name) {
79
+ if (!input.isTTY) {
80
+ throw new Error('Project name is required in non-interactive mode. Pass it as first argument.');
81
+ }
82
+
83
+ const rl = readline.createInterface({ input, output });
84
+ try {
85
+ nextOptions.name = await rl.question('Project name: ');
86
+ } finally {
87
+ await rl.close();
88
+ }
89
+ }
90
+
91
+ if (!nextOptions.yes && nextOptions.dbPrisma === undefined) {
92
+ nextOptions.dbPrisma = await promptSelect({
93
+ message: 'Enable db-prisma:',
94
+ defaultValue: DEFAULT_DB_PRISMA_ENABLED ? 'true' : 'false',
95
+ choices: [
96
+ { label: 'true', value: 'true' },
97
+ { label: 'false', value: 'false' },
98
+ ],
99
+ });
100
+ }
101
+
102
+ if (!nextOptions.yes && nextOptions.i18n === undefined) {
103
+ nextOptions.i18n = await promptSelect({
104
+ message: 'Enable i18n:',
105
+ defaultValue: 'true',
106
+ choices: [
107
+ { label: 'true', value: 'true' },
108
+ { label: 'false', value: 'false' },
109
+ ],
110
+ });
111
+ }
112
+
113
+ if (!nextOptions.yes && !nextOptions.proxy) {
114
+ nextOptions.proxy = await promptSelect({
115
+ message: 'Reverse proxy preset:',
116
+ defaultValue: DEFAULT_PROXY,
117
+ choices: [
118
+ { label: 'caddy', value: 'caddy' },
119
+ { label: 'nginx', value: 'nginx' },
120
+ { label: 'none', value: 'none' },
121
+ ],
122
+ });
123
+ }
124
+
125
+ if (nextOptions.yes) {
126
+ if (nextOptions.dbPrisma === undefined) nextOptions.dbPrisma = DEFAULT_DB_PRISMA_ENABLED ? 'true' : 'false';
127
+ if (nextOptions.i18n === undefined) nextOptions.i18n = 'true';
128
+ if (!nextOptions.proxy) nextOptions.proxy = DEFAULT_PROXY;
129
+ }
130
+
131
+ if (nextOptions.dbPrisma === undefined) {
132
+ nextOptions.dbPrisma = DEFAULT_DB_PRISMA_ENABLED ? 'true' : 'false';
133
+ }
134
+ if (nextOptions.i18n === undefined) {
135
+ nextOptions.i18n = 'true';
136
+ }
137
+ if (!nextOptions.proxy) {
138
+ nextOptions.proxy = DEFAULT_PROXY;
139
+ }
140
+
141
+ return nextOptions;
142
+ }
@@ -5,26 +5,29 @@ import { parseCliArgs } from './options.mjs';
5
5
  describe('parseCliArgs', () => {
6
6
  it('parses positional name and supported inline options', () => {
7
7
  const { options, positional } = parseCliArgs([
8
- 'demo-app',
9
- '--i18n=false',
10
- '--proxy=caddy',
11
- '--install',
8
+ 'demo-app',
9
+ '--db-prisma=false',
10
+ '--i18n=false',
11
+ '--proxy=caddy',
12
+ '--install',
12
13
  '--yes',
13
14
  ]);
14
15
 
15
16
  assert.equal(positional[0], 'demo-app');
16
- assert.equal(options.i18n, 'false');
17
+ assert.equal(options.dbPrisma, 'false');
18
+ assert.equal(options.i18n, 'false');
17
19
  assert.equal(options.proxy, 'caddy');
18
20
  assert.equal(options.install, true);
19
21
  assert.equal(options.yes, true);
20
22
  });
21
23
 
22
24
  it('parses separated option values and negated i18n flag', () => {
23
- const { options } = parseCliArgs(['--proxy', 'none', '--no-i18n', '--help']);
24
-
25
- assert.equal(options.proxy, 'none');
26
- assert.equal(options.i18n, false);
27
- assert.equal(options.help, true);
25
+ const { options } = parseCliArgs(['--proxy', 'none', '--no-i18n', '--no-db-prisma', '--help']);
26
+
27
+ assert.equal(options.proxy, 'none');
28
+ assert.equal(options.i18n, false);
29
+ assert.equal(options.dbPrisma, false);
30
+ assert.equal(options.help, true);
28
31
  });
29
32
 
30
33
  it('throws for removed stack-selection flags', () => {
package/src/constants.mjs CHANGED
@@ -1,12 +1,14 @@
1
- export const DEFAULT_FRONTEND = 'react';
2
- export const DEFAULT_DB = 'prisma';
3
- export const DEFAULT_PROXY = 'caddy';
4
- export const FIXED_DOCKER_ENABLED = true;
5
-
6
- export const DEFAULT_OPTIONS = {
7
- name: undefined,
8
- i18n: undefined,
9
- proxy: undefined,
1
+ export const DEFAULT_FRONTEND = 'react';
2
+ export const DEFAULT_DB = 'prisma';
3
+ export const DEFAULT_DB_PRISMA_ENABLED = true;
4
+ export const DEFAULT_PROXY = 'caddy';
5
+ export const FIXED_DOCKER_ENABLED = true;
6
+
7
+ export const DEFAULT_OPTIONS = {
8
+ name: undefined,
9
+ dbPrisma: undefined,
10
+ i18n: undefined,
11
+ proxy: undefined,
10
12
  install: false,
11
13
  yes: false,
12
14
  help: false,
package/src/core/docs.mjs CHANGED
@@ -40,20 +40,26 @@ function writeDocFromFragments({
40
40
  fs.writeFileSync(absoluteOutputPath, content, 'utf8');
41
41
  }
42
42
 
43
- export function generateDocs(targetRoot, options, packageRoot) {
44
- const fragmentsRoot = path.resolve(packageRoot, 'templates', 'docs-fragments');
45
- const variables = {
46
- FRONTEND_LABEL: getFrontendLabel(options.frontend),
47
- DB_LABEL: getDatabaseLabel(options.db),
48
- I18N_STATUS: options.i18nEnabled ? 'enabled' : 'disabled',
49
- DOCKER_STATUS: 'enabled',
50
- PROXY_LABEL: options.proxy,
51
- PROXY_CONFIG_PATH: getProxyConfigPath(options.proxy),
52
- };
53
-
54
- const readmeFragments = ['00_title', '10_stack', '20_quick_start_dev_intro'];
55
- readmeFragments.push('21_quick_start_dev_db_docker');
56
- readmeFragments.push('22_quick_start_dev_outro');
43
+ export function generateDocs(targetRoot, options, packageRoot) {
44
+ const fragmentsRoot = path.resolve(packageRoot, 'templates', 'docs-fragments');
45
+ const dbLabel = options.dbPrismaEnabled ? getDatabaseLabel(options.db) : 'none (add db-prisma later)';
46
+ const variables = {
47
+ FRONTEND_LABEL: getFrontendLabel(options.frontend),
48
+ DB_LABEL: dbLabel,
49
+ I18N_STATUS: options.i18nEnabled ? 'enabled' : 'disabled',
50
+ DB_PRISMA_STATUS: options.dbPrismaEnabled ? 'enabled' : 'disabled',
51
+ DOCKER_STATUS: 'enabled',
52
+ PROXY_LABEL: options.proxy,
53
+ PROXY_CONFIG_PATH: getProxyConfigPath(options.proxy),
54
+ };
55
+
56
+ const readmeFragments = ['00_title', '10_stack', '20_quick_start_dev_intro'];
57
+ if (options.dbPrismaEnabled) {
58
+ readmeFragments.push('21_quick_start_dev_db_docker');
59
+ } else {
60
+ readmeFragments.push('21_quick_start_dev_no_db');
61
+ }
62
+ readmeFragments.push('22_quick_start_dev_outro');
57
63
  readmeFragments.push(
58
64
  options.proxy === 'none' ? '30_quick_start_docker_none' : '30_quick_start_docker',
59
65
  );
@@ -64,16 +70,23 @@ export function generateDocs(targetRoot, options, packageRoot) {
64
70
  } else {
65
71
  readmeFragments.push('31_proxy_preset_none');
66
72
  }
67
- readmeFragments.push('32_prisma_container_start');
73
+ if (options.dbPrismaEnabled) {
74
+ readmeFragments.push('32_prisma_container_start');
75
+ }
68
76
  if (options.i18nEnabled) {
69
77
  readmeFragments.push('40_i18n');
70
78
  }
71
79
  readmeFragments.push('41_error_handling');
72
80
  readmeFragments.push('90_next_steps');
73
81
 
74
- const aiProjectFragments = ['00_title', '10_what_is', '20_structure_base'];
75
- if (options.i18nEnabled) {
76
- aiProjectFragments.push('21_structure_i18n');
82
+ const aiProjectFragments = ['00_title', '10_what_is', '20_structure_base'];
83
+ if (options.dbPrismaEnabled) {
84
+ aiProjectFragments.push('20b_structure_db_prisma');
85
+ } else {
86
+ aiProjectFragments.push('20b_structure_db_none');
87
+ }
88
+ if (options.i18nEnabled) {
89
+ aiProjectFragments.push('21_structure_i18n');
77
90
  }
78
91
  aiProjectFragments.push('22_structure_docker', '23_structure_docs', '30_run_dev', '31_run_docker');
79
92
  if (options.proxy === 'none') {
@@ -90,17 +103,25 @@ export function generateDocs(targetRoot, options, packageRoot) {
90
103
  aiProjectFragments.push('41_change_boundaries_docker');
91
104
  }
92
105
 
93
- const aiArchitectureFragments = ['00_title', '10_layout_base', '11_layout_infra'];
94
- if (options.i18nEnabled) {
95
- aiArchitectureFragments.push('12_layout_i18n_resources');
96
- }
106
+ const aiArchitectureFragments = ['00_title', '10_layout_base', '11_layout_infra'];
107
+ if (options.i18nEnabled) {
108
+ aiArchitectureFragments.push('12_layout_i18n_resources');
109
+ }
97
110
  aiArchitectureFragments.push('20_env_base');
111
+ if (options.dbPrismaEnabled) {
112
+ aiArchitectureFragments.push('20b_env_db_prisma');
113
+ }
98
114
  if (options.i18nEnabled) {
99
115
  aiArchitectureFragments.push('21_env_i18n');
100
116
  }
101
117
  aiArchitectureFragments.push('22_ts_module_policy');
102
118
  aiArchitectureFragments.push('23_error_handling');
103
- aiArchitectureFragments.push('30_default_db', '31_docker_runtime', '32_scope_freeze');
119
+ if (options.dbPrismaEnabled) {
120
+ aiArchitectureFragments.push('30_default_db');
121
+ } else {
122
+ aiArchitectureFragments.push('30_default_db_none');
123
+ }
124
+ aiArchitectureFragments.push('31_docker_runtime', '32_scope_freeze');
104
125
  aiArchitectureFragments.push('40_docs_generation', '50_extension_points');
105
126
 
106
127
  writeDocFromFragments({
@@ -24,12 +24,13 @@ describe('generateDocs', () => {
24
24
  try {
25
25
  generateDocs(
26
26
  targetRoot,
27
- {
28
- frontend: 'react',
29
- db: 'prisma',
30
- dockerEnabled: true,
31
- i18nEnabled: false,
32
- proxy: 'none',
27
+ {
28
+ frontend: 'react',
29
+ db: 'prisma',
30
+ dbPrismaEnabled: false,
31
+ dockerEnabled: true,
32
+ i18nEnabled: false,
33
+ proxy: 'none',
33
34
  },
34
35
  packageRoot,
35
36
  );
@@ -38,24 +39,26 @@ describe('generateDocs', () => {
38
39
  const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
39
40
  const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
40
41
 
41
- assert.match(readme, /Docker\/infra: `enabled`/);
42
+ assert.match(readme, /db-prisma`: `disabled`/);
43
+ assert.match(readme, /No DB module is enabled by default/);
42
44
  assert.match(readme, /Quick Start \(Docker\)/);
43
45
  assert.match(readme, /Proxy Preset: none/);
44
46
  assert.match(readme, /Error Handling \(`core-errors`\)/);
45
47
  assert.doesNotMatch(readme, /i18n Configuration/);
48
+ assert.doesNotMatch(readme, /Prisma In Container Start/);
46
49
 
47
50
  assert.match(projectDoc, /### Docker mode/);
48
51
  assert.match(projectDoc, /Active proxy preset: `none`/);
49
52
  assert.match(projectDoc, /CoreErrorsModule/);
50
53
  assert.doesNotMatch(projectDoc, /packages\/i18n/);
51
54
 
52
- assert.match(architectureDoc, /infra\/\*/);
55
+ assert.match(architectureDoc, /generated without `db-prisma`/);
53
56
  assert.doesNotMatch(architectureDoc, /I18N_ENABLED/);
54
57
  assert.match(architectureDoc, /API_PREFIX/);
55
58
  assert.match(architectureDoc, /Config Strategy/);
56
59
  assert.match(architectureDoc, /TypeScript Module Policy/);
57
60
  assert.match(architectureDoc, /tsconfig\.base\.esm\.json/);
58
- assert.match(architectureDoc, /DbPrismaModule/);
61
+ assert.doesNotMatch(architectureDoc, /DbPrismaModule/);
59
62
  } finally {
60
63
  fs.rmSync(targetRoot, { recursive: true, force: true });
61
64
  }
@@ -67,12 +70,13 @@ describe('generateDocs', () => {
67
70
  try {
68
71
  generateDocs(
69
72
  targetRoot,
70
- {
71
- frontend: 'react',
72
- db: 'prisma',
73
- dockerEnabled: true,
74
- i18nEnabled: true,
75
- proxy: 'caddy',
73
+ {
74
+ frontend: 'react',
75
+ db: 'prisma',
76
+ dbPrismaEnabled: true,
77
+ dockerEnabled: true,
78
+ i18nEnabled: true,
79
+ proxy: 'caddy',
76
80
  },
77
81
  packageRoot,
78
82
  );
@@ -84,6 +88,8 @@ describe('generateDocs', () => {
84
88
  assert.match(readme, /Quick Start \(Docker\)/);
85
89
  assert.match(readme, /Proxy Preset: Caddy/);
86
90
  assert.match(readme, /i18n Configuration/);
91
+ assert.match(readme, /db-prisma`: `enabled`/);
92
+ assert.match(readme, /Prisma In Container Start/);
87
93
  assert.match(readme, /Error Handling \(`core-errors`\)/);
88
94
 
89
95
  assert.match(projectDoc, /`infra` - Docker Compose \(always\) \+ proxy preset \(`caddy`\)/);
@@ -5,16 +5,19 @@ import { toKebabCase } from '../utils/values.mjs';
5
5
  import { applyI18nDisabled, applyProxyPreset, patchDockerEnvForI18n } from '../presets/index.mjs';
6
6
  import { applyModulePreset } from '../modules/executor.mjs';
7
7
  import { generateDocs } from './docs.mjs';
8
-
9
- function writeApiEnvExample(targetRoot, i18nEnabled) {
10
- const apiEnvExamplePath = path.join(targetRoot, 'apps', 'api', '.env.example');
8
+
9
+ function writeApiEnvExample(targetRoot, i18nEnabled, dbPrismaEnabled) {
10
+ const apiEnvExamplePath = path.join(targetRoot, 'apps', 'api', '.env.example');
11
11
  const apiEnvLines = [
12
12
  'NODE_ENV=development',
13
13
  'PORT=3000',
14
14
  'API_PREFIX=api',
15
- 'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app?schema=public',
16
15
  ];
17
-
16
+
17
+ if (dbPrismaEnabled) {
18
+ apiEnvLines.push('DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app?schema=public');
19
+ }
20
+
18
21
  if (i18nEnabled) {
19
22
  apiEnvLines.push('I18N_DEFAULT_LANG=en');
20
23
  apiEnvLines.push('I18N_FALLBACK_LANG=en');
@@ -39,12 +42,13 @@ export function scaffoldProject({
39
42
  templateRoot,
40
43
  packageRoot,
41
44
  targetRoot,
42
- projectName,
43
- frontend,
44
- db,
45
- i18nEnabled,
46
- proxy,
47
- }) {
45
+ projectName,
46
+ frontend,
47
+ db,
48
+ dbPrismaEnabled,
49
+ i18nEnabled,
50
+ proxy,
51
+ }) {
48
52
  copyRecursive(templateRoot, targetRoot);
49
53
  patchRootPackageJson(targetRoot, projectName);
50
54
  applyProxyPreset(targetRoot, proxy);
@@ -55,7 +59,15 @@ export function scaffoldProject({
55
59
  patchDockerEnvForI18n(targetRoot, i18nEnabled);
56
60
  applyI18nDisabled(targetRoot);
57
61
  }
58
-
59
- writeApiEnvExample(targetRoot, i18nEnabled);
60
- generateDocs(targetRoot, { frontend, db, dockerEnabled: true, i18nEnabled, proxy }, packageRoot);
61
- }
62
+
63
+ if (dbPrismaEnabled) {
64
+ applyModulePreset({ moduleId: 'db-prisma', targetRoot, packageRoot });
65
+ }
66
+
67
+ writeApiEnvExample(targetRoot, i18nEnabled, dbPrismaEnabled);
68
+ generateDocs(
69
+ targetRoot,
70
+ { frontend, db, dbPrismaEnabled, dockerEnabled: true, i18nEnabled, proxy },
71
+ packageRoot,
72
+ );
73
+ }