create-forgeon 0.1.2 → 0.1.5

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 (143) hide show
  1. package/README.md +19 -17
  2. package/bin/create-forgeon.mjs +22 -22
  3. package/package.json +1 -1
  4. package/src/cli/add-help.mjs +12 -12
  5. package/src/cli/add-options.mjs +54 -54
  6. package/src/cli/add-options.test.mjs +24 -24
  7. package/src/cli/help.mjs +20 -20
  8. package/src/cli/options.mjs +121 -121
  9. package/src/cli/options.test.mjs +41 -41
  10. package/src/cli/prompt-select.mjs +94 -94
  11. package/src/cli/prompt-select.test.mjs +148 -148
  12. package/src/constants.mjs +13 -13
  13. package/src/core/docs.mjs +128 -128
  14. package/src/core/docs.test.mjs +91 -91
  15. package/src/core/install.mjs +14 -14
  16. package/src/core/scaffold.mjs +48 -45
  17. package/src/core/validate.mjs +12 -12
  18. package/src/core/validate.test.mjs +73 -73
  19. package/src/databases/index.mjs +26 -26
  20. package/src/frameworks/index.mjs +32 -32
  21. package/src/infrastructure/proxy.mjs +12 -12
  22. package/src/modules/docs.mjs +70 -70
  23. package/src/modules/executor.mjs +39 -21
  24. package/src/modules/executor.test.mjs +95 -45
  25. package/src/modules/i18n.mjs +246 -0
  26. package/src/modules/registry.mjs +43 -35
  27. package/src/presets/i18n.mjs +211 -185
  28. package/src/presets/index.mjs +2 -2
  29. package/src/presets/proxy.mjs +32 -32
  30. package/src/run-add-module.mjs +47 -47
  31. package/src/run-create-forgeon.mjs +72 -72
  32. package/src/utils/fs.mjs +26 -26
  33. package/src/utils/values.mjs +20 -20
  34. package/templates/base/.dockerignore +7 -7
  35. package/templates/base/.editorconfig +11 -11
  36. package/templates/base/README.md +46 -46
  37. package/templates/base/apps/api/Dockerfile +24 -24
  38. package/templates/base/apps/api/package.json +39 -39
  39. package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +11 -11
  40. package/templates/base/apps/api/prisma/schema.prisma +14 -14
  41. package/templates/base/apps/api/prisma/seed.ts +19 -19
  42. package/templates/base/apps/api/src/app.module.ts +32 -32
  43. package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +5 -5
  44. package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +129 -129
  45. package/templates/base/apps/api/src/config/app.config.ts +12 -12
  46. package/templates/base/apps/api/src/health/health.controller.ts +30 -30
  47. package/templates/base/apps/api/src/main.ts +25 -25
  48. package/templates/base/apps/api/src/prisma/prisma.module.ts +8 -8
  49. package/templates/base/apps/api/src/prisma/prisma.service.ts +26 -26
  50. package/templates/base/apps/api/tsconfig.build.json +8 -8
  51. package/templates/base/apps/api/tsconfig.json +8 -8
  52. package/templates/base/apps/web/Dockerfile +12 -12
  53. package/templates/base/apps/web/index.html +11 -11
  54. package/templates/base/apps/web/package.json +21 -21
  55. package/templates/base/apps/web/src/App.tsx +35 -35
  56. package/templates/base/apps/web/src/main.tsx +8 -8
  57. package/templates/base/apps/web/src/styles.css +32 -32
  58. package/templates/base/apps/web/tsconfig.json +17 -17
  59. package/templates/base/apps/web/vite.config.ts +14 -14
  60. package/templates/base/docs/AI/ARCHITECTURE.md +37 -37
  61. package/templates/base/docs/AI/MODULE_SPEC.md +56 -56
  62. package/templates/base/docs/AI/PROJECT.md +31 -31
  63. package/templates/base/docs/AI/TASKS.md +57 -57
  64. package/templates/base/docs/README.md +6 -6
  65. package/templates/base/infra/caddy/Caddyfile +15 -15
  66. package/templates/base/infra/docker/.env.example +9 -9
  67. package/templates/base/infra/docker/caddy.Dockerfile +15 -15
  68. package/templates/base/infra/docker/compose.caddy.yml +44 -44
  69. package/templates/base/infra/docker/compose.nginx.yml +44 -44
  70. package/templates/base/infra/docker/compose.none.yml +37 -37
  71. package/templates/base/infra/docker/compose.yml +44 -44
  72. package/templates/base/infra/docker/nginx.Dockerfile +15 -15
  73. package/templates/base/infra/nginx/nginx.conf +31 -31
  74. package/templates/base/package.json +23 -23
  75. package/templates/base/packages/core/README.md +2 -2
  76. package/templates/base/packages/core/package.json +13 -13
  77. package/templates/base/packages/core/tsconfig.json +7 -7
  78. package/templates/base/packages/i18n/package.json +18 -18
  79. package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +45 -45
  80. package/templates/base/packages/i18n/tsconfig.json +8 -8
  81. package/templates/base/pnpm-workspace.yaml +2 -2
  82. package/templates/base/resources/i18n/en/common.json +4 -4
  83. package/templates/base/resources/i18n/en/validation.json +2 -2
  84. package/templates/base/resources/i18n/uk/common.json +4 -4
  85. package/templates/base/resources/i18n/uk/validation.json +2 -2
  86. package/templates/base/tsconfig.base.json +16 -16
  87. package/templates/docs-fragments/AI_ARCHITECTURE/00_title.md +1 -1
  88. package/templates/docs-fragments/AI_ARCHITECTURE/10_layout_base.md +6 -6
  89. package/templates/docs-fragments/AI_ARCHITECTURE/11_layout_infra.md +1 -1
  90. package/templates/docs-fragments/AI_ARCHITECTURE/12_layout_i18n_resources.md +1 -1
  91. package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +4 -4
  92. package/templates/docs-fragments/AI_ARCHITECTURE/21_env_i18n.md +3 -3
  93. package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +7 -7
  94. package/templates/docs-fragments/AI_ARCHITECTURE/31_docker_runtime.md +5 -5
  95. package/templates/docs-fragments/AI_ARCHITECTURE/32_scope_freeze.md +5 -5
  96. package/templates/docs-fragments/AI_ARCHITECTURE/40_docs_generation.md +9 -9
  97. package/templates/docs-fragments/AI_ARCHITECTURE/50_extension_points.md +8 -8
  98. package/templates/docs-fragments/AI_PROJECT/00_title.md +1 -1
  99. package/templates/docs-fragments/AI_PROJECT/10_what_is.md +3 -3
  100. package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +5 -5
  101. package/templates/docs-fragments/AI_PROJECT/21_structure_i18n.md +2 -0
  102. package/templates/docs-fragments/AI_PROJECT/22_structure_docker.md +1 -1
  103. package/templates/docs-fragments/AI_PROJECT/23_structure_docs.md +1 -1
  104. package/templates/docs-fragments/AI_PROJECT/30_run_dev.md +8 -8
  105. package/templates/docs-fragments/AI_PROJECT/31_run_docker.md +5 -5
  106. package/templates/docs-fragments/AI_PROJECT/32_proxy_notes.md +5 -5
  107. package/templates/docs-fragments/AI_PROJECT/32_proxy_notes_none.md +5 -5
  108. package/templates/docs-fragments/AI_PROJECT/33_i18n_notes.md +2 -0
  109. package/templates/docs-fragments/AI_PROJECT/40_change_boundaries_base.md +3 -3
  110. package/templates/docs-fragments/AI_PROJECT/41_change_boundaries_docker.md +1 -1
  111. package/templates/docs-fragments/README/00_title.md +3 -3
  112. package/templates/docs-fragments/README/10_stack.md +8 -8
  113. package/templates/docs-fragments/README/20_quick_start_dev_intro.md +6 -6
  114. package/templates/docs-fragments/README/21_quick_start_dev_db_docker.md +4 -4
  115. package/templates/docs-fragments/README/21_quick_start_dev_db_local.md +1 -1
  116. package/templates/docs-fragments/README/22_quick_start_dev_outro.md +7 -7
  117. package/templates/docs-fragments/README/30_quick_start_docker.md +7 -7
  118. package/templates/docs-fragments/README/30_quick_start_docker_none.md +9 -9
  119. package/templates/docs-fragments/README/31_proxy_preset_caddy.md +9 -9
  120. package/templates/docs-fragments/README/31_proxy_preset_nginx.md +8 -8
  121. package/templates/docs-fragments/README/31_proxy_preset_none.md +6 -6
  122. package/templates/docs-fragments/README/32_prisma_container_start.md +5 -5
  123. package/templates/docs-fragments/README/40_i18n.md +14 -8
  124. package/templates/docs-fragments/README/90_next_steps.md +7 -7
  125. package/templates/module-fragments/i18n/00_title.md +5 -0
  126. package/templates/module-fragments/i18n/10_overview.md +9 -0
  127. package/templates/module-fragments/i18n/20_scope.md +7 -0
  128. package/templates/module-fragments/i18n/90_status_implemented.md +3 -0
  129. package/templates/module-fragments/jwt-auth/00_title.md +1 -1
  130. package/templates/module-fragments/jwt-auth/10_overview.md +6 -6
  131. package/templates/module-fragments/jwt-auth/20_scope.md +7 -7
  132. package/templates/module-fragments/jwt-auth/90_status_planned.md +3 -3
  133. package/templates/module-fragments/queue/00_title.md +1 -1
  134. package/templates/module-fragments/queue/10_overview.md +6 -6
  135. package/templates/module-fragments/queue/20_scope.md +7 -7
  136. package/templates/module-fragments/queue/90_status_planned.md +3 -3
  137. package/templates/module-presets/i18n/apps/web/src/App.tsx +61 -0
  138. package/templates/module-presets/i18n/packages/i18n-contracts/package.json +14 -0
  139. package/templates/module-presets/i18n/packages/i18n-contracts/src/index.ts +7 -0
  140. package/templates/module-presets/i18n/packages/i18n-contracts/tsconfig.json +8 -0
  141. package/templates/module-presets/i18n/packages/i18n-web/package.json +17 -0
  142. package/templates/module-presets/i18n/packages/i18n-web/src/index.ts +50 -0
  143. package/templates/module-presets/i18n/packages/i18n-web/tsconfig.json +8 -0
package/README.md CHANGED
@@ -1,23 +1,25 @@
1
- # create-forgeon
2
-
3
- CLI package for generating Forgeon fullstack monorepo projects.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- npx create-forgeon@latest my-app --i18n true --proxy caddy
9
- ```
10
-
11
- If flags are omitted, the CLI asks interactive questions.
12
- Project name stays text input; fixed-choice prompts use arrow-key selection (`Up/Down + Enter`).
13
-
1
+ # create-forgeon
2
+
3
+ CLI package for generating Forgeon fullstack monorepo projects.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-forgeon@latest my-app --i18n true --proxy caddy
9
+ ```
10
+
11
+ If flags are omitted, the CLI asks interactive questions.
12
+ Project name stays text input; fixed-choice prompts use arrow-key selection (`Up/Down + Enter`).
13
+
14
14
  ```bash
15
15
  npx create-forgeon@latest add --list
16
+ npx create-forgeon@latest add i18n --project ./my-app
16
17
  npx create-forgeon@latest add jwt-auth --project ./my-app
17
18
  ```
18
-
19
- ## Notes
20
-
19
+
20
+ ## Notes
21
+
21
22
  - Canonical stack is fixed: NestJS + React + Prisma/Postgres + Docker.
22
23
  - Reverse proxy options: `caddy` (default), `nginx`, `none`.
23
- - `add <module-id>` currently writes module docs notes under `docs/AI/MODULES/`
24
+ - `add i18n` is implemented and applies backend/frontend i18n wiring.
25
+ - Planned modules write docs notes under `docs/AI/MODULES/`.
@@ -1,22 +1,22 @@
1
- #!/usr/bin/env node
2
- import { runCreateForgeon } from '../src/run-create-forgeon.mjs';
3
- import { runAddModule } from '../src/run-add-module.mjs';
4
-
5
- const args = process.argv.slice(2);
6
- const command = args[0];
7
-
8
- const task =
9
- command === 'add'
10
- ? runAddModule(args.slice(1))
11
- : runCreateForgeon(args);
12
-
13
- task.then(() => {
14
- if (typeof process.stdin.pause === 'function') {
15
- process.stdin.pause();
16
- }
17
- });
18
-
19
- task.catch((error) => {
20
- console.error(error instanceof Error ? error.message : error);
21
- process.exit(1);
22
- });
1
+ #!/usr/bin/env node
2
+ import { runCreateForgeon } from '../src/run-create-forgeon.mjs';
3
+ import { runAddModule } from '../src/run-add-module.mjs';
4
+
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+
8
+ const task =
9
+ command === 'add'
10
+ ? runAddModule(args.slice(1))
11
+ : runCreateForgeon(args);
12
+
13
+ task.then(() => {
14
+ if (typeof process.stdin.pause === 'function') {
15
+ process.stdin.pause();
16
+ }
17
+ });
18
+
19
+ task.catch((error) => {
20
+ console.error(error instanceof Error ? error.message : error);
21
+ process.exit(1);
22
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -1,12 +1,12 @@
1
- export function printAddHelp() {
2
- console.log(`create-forgeon add
3
-
4
- Usage:
5
- npx create-forgeon@latest add <module-id> [options]
6
-
7
- Options:
8
- --project <path> Target project path (default: current directory)
9
- --list List available modules
10
- -h, --help Show this help
11
- `);
12
- }
1
+ export function printAddHelp() {
2
+ console.log(`create-forgeon add
3
+
4
+ Usage:
5
+ npx create-forgeon@latest add <module-id> [options]
6
+
7
+ Options:
8
+ --project <path> Target project path (default: current directory)
9
+ --list List available modules
10
+ -h, --help Show this help
11
+ `);
12
+ }
@@ -1,54 +1,54 @@
1
- export function parseAddCliArgs(argv) {
2
- const args = [...argv];
3
- const options = {
4
- moduleId: undefined,
5
- project: '.',
6
- list: false,
7
- help: false,
8
- };
9
- const positional = [];
10
-
11
- for (let i = 0; i < args.length; i += 1) {
12
- const arg = args[i];
13
- if (arg === '--') continue;
14
-
15
- if (arg === '-h' || arg === '--help') {
16
- options.help = true;
17
- continue;
18
- }
19
-
20
- if (arg === '--list') {
21
- options.list = true;
22
- continue;
23
- }
24
-
25
- if (arg.startsWith('--project=')) {
26
- options.project = arg.split('=')[1] || '.';
27
- continue;
28
- }
29
-
30
- if (arg === '--project') {
31
- if (args[i + 1] && !args[i + 1].startsWith('-')) {
32
- options.project = args[i + 1];
33
- i += 1;
34
- }
35
- continue;
36
- }
37
-
38
- if (arg.startsWith('--')) {
39
- continue;
40
- }
41
-
42
- positional.push(arg);
43
- }
44
-
45
- if (positional.length > 0) {
46
- [options.moduleId] = positional;
47
- }
48
-
49
- if (positional.length > 1 && options.project === '.') {
50
- options.project = positional[1];
51
- }
52
-
53
- return options;
54
- }
1
+ export function parseAddCliArgs(argv) {
2
+ const args = [...argv];
3
+ const options = {
4
+ moduleId: undefined,
5
+ project: '.',
6
+ list: false,
7
+ help: false,
8
+ };
9
+ const positional = [];
10
+
11
+ for (let i = 0; i < args.length; i += 1) {
12
+ const arg = args[i];
13
+ if (arg === '--') continue;
14
+
15
+ if (arg === '-h' || arg === '--help') {
16
+ options.help = true;
17
+ continue;
18
+ }
19
+
20
+ if (arg === '--list') {
21
+ options.list = true;
22
+ continue;
23
+ }
24
+
25
+ if (arg.startsWith('--project=')) {
26
+ options.project = arg.split('=')[1] || '.';
27
+ continue;
28
+ }
29
+
30
+ if (arg === '--project') {
31
+ if (args[i + 1] && !args[i + 1].startsWith('-')) {
32
+ options.project = args[i + 1];
33
+ i += 1;
34
+ }
35
+ continue;
36
+ }
37
+
38
+ if (arg.startsWith('--')) {
39
+ continue;
40
+ }
41
+
42
+ positional.push(arg);
43
+ }
44
+
45
+ if (positional.length > 0) {
46
+ [options.moduleId] = positional;
47
+ }
48
+
49
+ if (positional.length > 1 && options.project === '.') {
50
+ options.project = positional[1];
51
+ }
52
+
53
+ return options;
54
+ }
@@ -1,24 +1,24 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { parseAddCliArgs } from './add-options.mjs';
4
-
5
- describe('parseAddCliArgs', () => {
6
- it('parses module id and explicit project', () => {
7
- const options = parseAddCliArgs(['jwt-auth', '--project', './demo']);
8
- assert.equal(options.moduleId, 'jwt-auth');
9
- assert.equal(options.project, './demo');
10
- assert.equal(options.list, false);
11
- });
12
-
13
- it('parses --list and --help', () => {
14
- const options = parseAddCliArgs(['--list', '--help']);
15
- assert.equal(options.list, true);
16
- assert.equal(options.help, true);
17
- });
18
-
19
- it('uses second positional as project when project flag is absent', () => {
20
- const options = parseAddCliArgs(['queue', './my-app']);
21
- assert.equal(options.moduleId, 'queue');
22
- assert.equal(options.project, './my-app');
23
- });
24
- });
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseAddCliArgs } from './add-options.mjs';
4
+
5
+ describe('parseAddCliArgs', () => {
6
+ it('parses module id and explicit project', () => {
7
+ const options = parseAddCliArgs(['jwt-auth', '--project', './demo']);
8
+ assert.equal(options.moduleId, 'jwt-auth');
9
+ assert.equal(options.project, './demo');
10
+ assert.equal(options.list, false);
11
+ });
12
+
13
+ it('parses --list and --help', () => {
14
+ const options = parseAddCliArgs(['--list', '--help']);
15
+ assert.equal(options.list, true);
16
+ assert.equal(options.help, true);
17
+ });
18
+
19
+ it('uses second positional as project when project flag is absent', () => {
20
+ const options = parseAddCliArgs(['queue', './my-app']);
21
+ assert.equal(options.moduleId, 'queue');
22
+ assert.equal(options.project, './my-app');
23
+ });
24
+ });
package/src/cli/help.mjs CHANGED
@@ -1,20 +1,20 @@
1
- export function printHelp() {
2
- console.log(`create-forgeon
3
-
4
- Usage:
5
- npx create-forgeon@latest <project-name> [options]
6
- npx create-forgeon@latest add <module-id> [options]
7
- npx create-forgeon@latest add --list
8
-
9
- Create options:
10
- --i18n <true|false> Enable i18n (default: true)
11
- --proxy <caddy|nginx|none> Reverse proxy preset (default: caddy)
12
- --install Run pnpm install after generation
13
- -y, --yes Skip prompts and use defaults
14
- -h, --help Show this help
15
-
16
- Add options:
17
- --project <path> Target project path (default: current directory)
18
- --list List available modules
19
- `);
20
- }
1
+ export function printHelp() {
2
+ console.log(`create-forgeon
3
+
4
+ Usage:
5
+ npx create-forgeon@latest <project-name> [options]
6
+ npx create-forgeon@latest add <module-id> [options]
7
+ npx create-forgeon@latest add --list
8
+
9
+ Create options:
10
+ --i18n <true|false> Enable i18n (default: true)
11
+ --proxy <caddy|nginx|none> Reverse proxy preset (default: caddy)
12
+ --install Run pnpm install after generation
13
+ -y, --yes Skip prompts and use defaults
14
+ -h, --help Show this help
15
+
16
+ Add options:
17
+ --project <path> Target project path (default: current directory)
18
+ --list List available modules
19
+ `);
20
+ }
@@ -1,121 +1,121 @@
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_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,41 +1,41 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { parseCliArgs } from './options.mjs';
4
-
5
- describe('parseCliArgs', () => {
6
- it('parses positional name and supported inline options', () => {
7
- const { options, positional } = parseCliArgs([
8
- 'demo-app',
9
- '--i18n=false',
10
- '--proxy=caddy',
11
- '--install',
12
- '--yes',
13
- ]);
14
-
15
- assert.equal(positional[0], 'demo-app');
16
- assert.equal(options.i18n, 'false');
17
- assert.equal(options.proxy, 'caddy');
18
- assert.equal(options.install, true);
19
- assert.equal(options.yes, true);
20
- });
21
-
22
- 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);
28
- });
29
-
30
- it('throws for removed stack-selection flags', () => {
31
- assert.throws(
32
- () => parseCliArgs(['demo', '--frontend=react']),
33
- /Option "--frontend" has been removed/,
34
- );
35
- assert.throws(() => parseCliArgs(['demo', '--db=prisma']), /Option "--db" has been removed/);
36
- assert.throws(
37
- () => parseCliArgs(['demo', '--docker=true']),
38
- /Option "--docker" has been removed/,
39
- );
40
- });
41
- });
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseCliArgs } from './options.mjs';
4
+
5
+ describe('parseCliArgs', () => {
6
+ it('parses positional name and supported inline options', () => {
7
+ const { options, positional } = parseCliArgs([
8
+ 'demo-app',
9
+ '--i18n=false',
10
+ '--proxy=caddy',
11
+ '--install',
12
+ '--yes',
13
+ ]);
14
+
15
+ assert.equal(positional[0], 'demo-app');
16
+ assert.equal(options.i18n, 'false');
17
+ assert.equal(options.proxy, 'caddy');
18
+ assert.equal(options.install, true);
19
+ assert.equal(options.yes, true);
20
+ });
21
+
22
+ 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);
28
+ });
29
+
30
+ it('throws for removed stack-selection flags', () => {
31
+ assert.throws(
32
+ () => parseCliArgs(['demo', '--frontend=react']),
33
+ /Option "--frontend" has been removed/,
34
+ );
35
+ assert.throws(() => parseCliArgs(['demo', '--db=prisma']), /Option "--db" has been removed/);
36
+ assert.throws(
37
+ () => parseCliArgs(['demo', '--docker=true']),
38
+ /Option "--docker" has been removed/,
39
+ );
40
+ });
41
+ });