create-forgeon 0.1.37 → 0.2.0
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 +12 -4
- package/package.json +1 -1
- package/src/cli/help.mjs +3 -2
- package/src/cli/options.mjs +142 -121
- package/src/cli/options.test.mjs +13 -10
- package/src/constants.mjs +11 -9
- package/src/core/docs.mjs +44 -23
- package/src/core/docs.test.mjs +21 -15
- package/src/core/scaffold.mjs +27 -15
- package/src/modules/db-prisma.mjs +108 -31
- package/src/modules/executor.test.mjs +51 -13
- package/src/modules/i18n.mjs +10 -27
- package/src/modules/logger.mjs +4 -1
- package/src/modules/swagger.mjs +7 -2
- package/src/presets/i18n.mjs +63 -40
- package/src/run-add-module.mjs +87 -17
- package/src/run-create-forgeon.mjs +33 -24
- package/templates/base/README.md +16 -3
- package/templates/base/apps/api/Dockerfile +6 -11
- package/templates/base/apps/api/package.json +13 -24
- package/templates/base/apps/api/src/app.module.ts +3 -5
- package/templates/base/apps/api/src/health/health.controller.ts +1 -19
- package/templates/base/apps/web/src/App.tsx +0 -5
- package/templates/base/docs/AI/MODULE_CHECKS.md +1 -1
- package/templates/base/docs/AI/ROADMAP.md +1 -1
- package/templates/base/infra/docker/.env.example +1 -6
- package/templates/base/infra/docker/compose.caddy.yml +13 -37
- package/templates/base/infra/docker/compose.nginx.yml +13 -37
- package/templates/base/infra/docker/compose.none.yml +8 -32
- package/templates/base/infra/docker/compose.yml +16 -40
- package/templates/base/package.json +12 -9
- package/templates/base/scripts/forgeon-sync-integrations.mjs +399 -0
- package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +0 -1
- package/templates/docs-fragments/AI_ARCHITECTURE/20b_env_db_prisma.md +1 -0
- package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +2 -2
- package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db_none.md +7 -0
- package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +0 -1
- package/templates/docs-fragments/AI_PROJECT/20b_structure_db_none.md +2 -0
- package/templates/docs-fragments/AI_PROJECT/20b_structure_db_prisma.md +1 -0
- package/templates/docs-fragments/README/10_stack.md +4 -4
- package/templates/docs-fragments/README/21_quick_start_dev_no_db.md +6 -0
- package/templates/module-presets/i18n/apps/web/src/App.tsx +0 -5
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.controller.ts +1 -1
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.service.ts +11 -2
- package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/login.dto.ts +1 -1
- package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/refresh.dto.ts +1 -1
- /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/migrations/0001_init/migration.sql +0 -0
- /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/migrations/migration_lock.toml +0 -0
- /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/schema.prisma +0 -0
- /package/templates/{base → module-presets/db-prisma}/apps/api/prisma/seed.ts +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/README.md +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/package.json +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma-config.loader.ts +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma-config.service.ts +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma-env.schema.ts +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/db-prisma.module.ts +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/index.ts +0 -0
- /package/templates/{base → module-presets/db-prisma}/packages/db-prisma/src/prisma.service.ts +0 -0
- /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 +
|
|
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
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
|
-
--
|
|
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
|
package/src/cli/options.mjs
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
i
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (nextOptions.
|
|
114
|
-
nextOptions.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|
package/src/cli/options.test.mjs
CHANGED
|
@@ -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
|
-
'--
|
|
10
|
-
'--
|
|
11
|
-
'--
|
|
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.
|
|
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.
|
|
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
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
readmeFragments
|
|
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
|
-
|
|
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.
|
|
76
|
-
aiProjectFragments.push('
|
|
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
|
-
|
|
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({
|
package/src/core/docs.test.mjs
CHANGED
|
@@ -24,12 +24,13 @@ describe('generateDocs', () => {
|
|
|
24
24
|
try {
|
|
25
25
|
generateDocs(
|
|
26
26
|
targetRoot,
|
|
27
|
-
{
|
|
28
|
-
frontend: 'react',
|
|
29
|
-
db: 'prisma',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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, /
|
|
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, /
|
|
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.
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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`\)/);
|
package/src/core/scaffold.mjs
CHANGED
|
@@ -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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
+
}
|