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.
- package/README.md +19 -17
- package/bin/create-forgeon.mjs +22 -22
- package/package.json +1 -1
- package/src/cli/add-help.mjs +12 -12
- package/src/cli/add-options.mjs +54 -54
- package/src/cli/add-options.test.mjs +24 -24
- package/src/cli/help.mjs +20 -20
- package/src/cli/options.mjs +121 -121
- package/src/cli/options.test.mjs +41 -41
- package/src/cli/prompt-select.mjs +94 -94
- package/src/cli/prompt-select.test.mjs +148 -148
- package/src/constants.mjs +13 -13
- package/src/core/docs.mjs +128 -128
- package/src/core/docs.test.mjs +91 -91
- package/src/core/install.mjs +14 -14
- package/src/core/scaffold.mjs +48 -45
- package/src/core/validate.mjs +12 -12
- package/src/core/validate.test.mjs +73 -73
- package/src/databases/index.mjs +26 -26
- package/src/frameworks/index.mjs +32 -32
- package/src/infrastructure/proxy.mjs +12 -12
- package/src/modules/docs.mjs +70 -70
- package/src/modules/executor.mjs +39 -21
- package/src/modules/executor.test.mjs +95 -45
- package/src/modules/i18n.mjs +246 -0
- package/src/modules/registry.mjs +43 -35
- package/src/presets/i18n.mjs +211 -185
- package/src/presets/index.mjs +2 -2
- package/src/presets/proxy.mjs +32 -32
- package/src/run-add-module.mjs +47 -47
- package/src/run-create-forgeon.mjs +72 -72
- package/src/utils/fs.mjs +26 -26
- package/src/utils/values.mjs +20 -20
- package/templates/base/.dockerignore +7 -7
- package/templates/base/.editorconfig +11 -11
- package/templates/base/README.md +46 -46
- package/templates/base/apps/api/Dockerfile +24 -24
- package/templates/base/apps/api/package.json +39 -39
- package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +11 -11
- package/templates/base/apps/api/prisma/schema.prisma +14 -14
- package/templates/base/apps/api/prisma/seed.ts +19 -19
- package/templates/base/apps/api/src/app.module.ts +32 -32
- package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +5 -5
- package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +129 -129
- package/templates/base/apps/api/src/config/app.config.ts +12 -12
- package/templates/base/apps/api/src/health/health.controller.ts +30 -30
- package/templates/base/apps/api/src/main.ts +25 -25
- package/templates/base/apps/api/src/prisma/prisma.module.ts +8 -8
- package/templates/base/apps/api/src/prisma/prisma.service.ts +26 -26
- package/templates/base/apps/api/tsconfig.build.json +8 -8
- package/templates/base/apps/api/tsconfig.json +8 -8
- package/templates/base/apps/web/Dockerfile +12 -12
- package/templates/base/apps/web/index.html +11 -11
- package/templates/base/apps/web/package.json +21 -21
- package/templates/base/apps/web/src/App.tsx +35 -35
- package/templates/base/apps/web/src/main.tsx +8 -8
- package/templates/base/apps/web/src/styles.css +32 -32
- package/templates/base/apps/web/tsconfig.json +17 -17
- package/templates/base/apps/web/vite.config.ts +14 -14
- package/templates/base/docs/AI/ARCHITECTURE.md +37 -37
- package/templates/base/docs/AI/MODULE_SPEC.md +56 -56
- package/templates/base/docs/AI/PROJECT.md +31 -31
- package/templates/base/docs/AI/TASKS.md +57 -57
- package/templates/base/docs/README.md +6 -6
- package/templates/base/infra/caddy/Caddyfile +15 -15
- package/templates/base/infra/docker/.env.example +9 -9
- package/templates/base/infra/docker/caddy.Dockerfile +15 -15
- package/templates/base/infra/docker/compose.caddy.yml +44 -44
- package/templates/base/infra/docker/compose.nginx.yml +44 -44
- package/templates/base/infra/docker/compose.none.yml +37 -37
- package/templates/base/infra/docker/compose.yml +44 -44
- package/templates/base/infra/docker/nginx.Dockerfile +15 -15
- package/templates/base/infra/nginx/nginx.conf +31 -31
- package/templates/base/package.json +23 -23
- package/templates/base/packages/core/README.md +2 -2
- package/templates/base/packages/core/package.json +13 -13
- package/templates/base/packages/core/tsconfig.json +7 -7
- package/templates/base/packages/i18n/package.json +18 -18
- package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +45 -45
- package/templates/base/packages/i18n/tsconfig.json +8 -8
- package/templates/base/pnpm-workspace.yaml +2 -2
- package/templates/base/resources/i18n/en/common.json +4 -4
- package/templates/base/resources/i18n/en/validation.json +2 -2
- package/templates/base/resources/i18n/uk/common.json +4 -4
- package/templates/base/resources/i18n/uk/validation.json +2 -2
- package/templates/base/tsconfig.base.json +16 -16
- package/templates/docs-fragments/AI_ARCHITECTURE/00_title.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/10_layout_base.md +6 -6
- package/templates/docs-fragments/AI_ARCHITECTURE/11_layout_infra.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/12_layout_i18n_resources.md +1 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +4 -4
- package/templates/docs-fragments/AI_ARCHITECTURE/21_env_i18n.md +3 -3
- package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +7 -7
- package/templates/docs-fragments/AI_ARCHITECTURE/31_docker_runtime.md +5 -5
- package/templates/docs-fragments/AI_ARCHITECTURE/32_scope_freeze.md +5 -5
- package/templates/docs-fragments/AI_ARCHITECTURE/40_docs_generation.md +9 -9
- package/templates/docs-fragments/AI_ARCHITECTURE/50_extension_points.md +8 -8
- package/templates/docs-fragments/AI_PROJECT/00_title.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/10_what_is.md +3 -3
- package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/21_structure_i18n.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/22_structure_docker.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/23_structure_docs.md +1 -1
- package/templates/docs-fragments/AI_PROJECT/30_run_dev.md +8 -8
- package/templates/docs-fragments/AI_PROJECT/31_run_docker.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/32_proxy_notes.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/32_proxy_notes_none.md +5 -5
- package/templates/docs-fragments/AI_PROJECT/33_i18n_notes.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/40_change_boundaries_base.md +3 -3
- package/templates/docs-fragments/AI_PROJECT/41_change_boundaries_docker.md +1 -1
- package/templates/docs-fragments/README/00_title.md +3 -3
- package/templates/docs-fragments/README/10_stack.md +8 -8
- package/templates/docs-fragments/README/20_quick_start_dev_intro.md +6 -6
- package/templates/docs-fragments/README/21_quick_start_dev_db_docker.md +4 -4
- package/templates/docs-fragments/README/21_quick_start_dev_db_local.md +1 -1
- package/templates/docs-fragments/README/22_quick_start_dev_outro.md +7 -7
- package/templates/docs-fragments/README/30_quick_start_docker.md +7 -7
- package/templates/docs-fragments/README/30_quick_start_docker_none.md +9 -9
- package/templates/docs-fragments/README/31_proxy_preset_caddy.md +9 -9
- package/templates/docs-fragments/README/31_proxy_preset_nginx.md +8 -8
- package/templates/docs-fragments/README/31_proxy_preset_none.md +6 -6
- package/templates/docs-fragments/README/32_prisma_container_start.md +5 -5
- package/templates/docs-fragments/README/40_i18n.md +14 -8
- package/templates/docs-fragments/README/90_next_steps.md +7 -7
- package/templates/module-fragments/i18n/00_title.md +5 -0
- package/templates/module-fragments/i18n/10_overview.md +9 -0
- package/templates/module-fragments/i18n/20_scope.md +7 -0
- package/templates/module-fragments/i18n/90_status_implemented.md +3 -0
- package/templates/module-fragments/jwt-auth/00_title.md +1 -1
- package/templates/module-fragments/jwt-auth/10_overview.md +6 -6
- package/templates/module-fragments/jwt-auth/20_scope.md +7 -7
- package/templates/module-fragments/jwt-auth/90_status_planned.md +3 -3
- package/templates/module-fragments/queue/00_title.md +1 -1
- package/templates/module-fragments/queue/10_overview.md +6 -6
- package/templates/module-fragments/queue/20_scope.md +7 -7
- package/templates/module-fragments/queue/90_status_planned.md +3 -3
- package/templates/module-presets/i18n/apps/web/src/App.tsx +61 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/package.json +14 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/src/index.ts +7 -0
- package/templates/module-presets/i18n/packages/i18n-contracts/tsconfig.json +8 -0
- package/templates/module-presets/i18n/packages/i18n-web/package.json +17 -0
- package/templates/module-presets/i18n/packages/i18n-web/src/index.ts +50 -0
- 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
|
|
24
|
+
- `add i18n` is implemented and applies backend/frontend i18n wiring.
|
|
25
|
+
- Planned modules write docs notes under `docs/AI/MODULES/`.
|
package/bin/create-forgeon.mjs
CHANGED
|
@@ -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
package/src/cli/add-help.mjs
CHANGED
|
@@ -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
|
+
}
|
package/src/cli/add-options.mjs
CHANGED
|
@@ -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
|
+
}
|
package/src/cli/options.mjs
CHANGED
|
@@ -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
|
+
}
|
package/src/cli/options.test.mjs
CHANGED
|
@@ -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
|
+
});
|