create-forgeon 0.1.2 → 0.1.6
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 +283 -0
- package/src/modules/registry.mjs +43 -35
- package/src/presets/i18n.mjs +228 -180
- 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/src/core/docs.mjs
CHANGED
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getDatabaseLabel } from '../databases/index.mjs';
|
|
4
|
-
import { getFrontendLabel } from '../frameworks/index.mjs';
|
|
5
|
-
import { getProxyConfigPath } from '../infrastructure/proxy.mjs';
|
|
6
|
-
|
|
7
|
-
function renderTemplate(content, variables) {
|
|
8
|
-
return content.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key) => String(variables[key] ?? ''));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function readFragment(fragmentsRoot, docKey, fragmentName, variables) {
|
|
12
|
-
const fragmentPath = path.join(fragmentsRoot, docKey, `${fragmentName}.md`);
|
|
13
|
-
if (!fs.existsSync(fragmentPath)) {
|
|
14
|
-
throw new Error(`Missing docs fragment: ${fragmentPath}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const raw = fs.readFileSync(fragmentPath, 'utf8').replace(/\r\n/g, '\n').trim();
|
|
18
|
-
return renderTemplate(raw, variables).trim();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function writeDocFromFragments({
|
|
22
|
-
targetRoot,
|
|
23
|
-
outputPath,
|
|
24
|
-
fragmentsRoot,
|
|
25
|
-
docKey,
|
|
26
|
-
fragmentNames,
|
|
27
|
-
variables,
|
|
28
|
-
}) {
|
|
29
|
-
const fragments = fragmentNames
|
|
30
|
-
.map((fragmentName) => readFragment(fragmentsRoot, docKey, fragmentName, variables))
|
|
31
|
-
.filter((fragment) => fragment.length > 0);
|
|
32
|
-
|
|
33
|
-
const content = `${fragments
|
|
34
|
-
.join('\n\n')
|
|
35
|
-
.replace(/\n{2,}(?=- )/g, '\n')
|
|
36
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
37
|
-
.trimEnd()}\n`;
|
|
38
|
-
const absoluteOutputPath = path.join(targetRoot, outputPath);
|
|
39
|
-
fs.mkdirSync(path.dirname(absoluteOutputPath), { recursive: true });
|
|
40
|
-
fs.writeFileSync(absoluteOutputPath, content, 'utf8');
|
|
41
|
-
}
|
|
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');
|
|
57
|
-
readmeFragments.push(
|
|
58
|
-
options.proxy === 'none' ? '30_quick_start_docker_none' : '30_quick_start_docker',
|
|
59
|
-
);
|
|
60
|
-
if (options.proxy === 'caddy') {
|
|
61
|
-
readmeFragments.push('31_proxy_preset_caddy');
|
|
62
|
-
} else if (options.proxy === 'nginx') {
|
|
63
|
-
readmeFragments.push('31_proxy_preset_nginx');
|
|
64
|
-
} else {
|
|
65
|
-
readmeFragments.push('31_proxy_preset_none');
|
|
66
|
-
}
|
|
67
|
-
readmeFragments.push('32_prisma_container_start');
|
|
68
|
-
if (options.i18nEnabled) {
|
|
69
|
-
readmeFragments.push('40_i18n');
|
|
70
|
-
}
|
|
71
|
-
readmeFragments.push('90_next_steps');
|
|
72
|
-
|
|
73
|
-
const aiProjectFragments = ['00_title', '10_what_is', '20_structure_base'];
|
|
74
|
-
if (options.i18nEnabled) {
|
|
75
|
-
aiProjectFragments.push('21_structure_i18n');
|
|
76
|
-
}
|
|
77
|
-
aiProjectFragments.push('22_structure_docker', '23_structure_docs', '30_run_dev', '31_run_docker');
|
|
78
|
-
if (options.proxy === 'none') {
|
|
79
|
-
aiProjectFragments.push('32_proxy_notes_none');
|
|
80
|
-
} else {
|
|
81
|
-
aiProjectFragments.push('32_proxy_notes');
|
|
82
|
-
}
|
|
83
|
-
if (options.i18nEnabled) {
|
|
84
|
-
aiProjectFragments.push('33_i18n_notes');
|
|
85
|
-
}
|
|
86
|
-
aiProjectFragments.push('40_change_boundaries_base');
|
|
87
|
-
if (options.proxy !== 'none') {
|
|
88
|
-
aiProjectFragments.push('41_change_boundaries_docker');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const aiArchitectureFragments = ['00_title', '10_layout_base', '11_layout_infra'];
|
|
92
|
-
if (options.i18nEnabled) {
|
|
93
|
-
aiArchitectureFragments.push('12_layout_i18n_resources');
|
|
94
|
-
}
|
|
95
|
-
aiArchitectureFragments.push('20_env_base');
|
|
96
|
-
if (options.i18nEnabled) {
|
|
97
|
-
aiArchitectureFragments.push('21_env_i18n');
|
|
98
|
-
}
|
|
99
|
-
aiArchitectureFragments.push('30_default_db', '31_docker_runtime', '32_scope_freeze');
|
|
100
|
-
aiArchitectureFragments.push('40_docs_generation', '50_extension_points');
|
|
101
|
-
|
|
102
|
-
writeDocFromFragments({
|
|
103
|
-
targetRoot,
|
|
104
|
-
outputPath: 'README.md',
|
|
105
|
-
fragmentsRoot,
|
|
106
|
-
docKey: 'README',
|
|
107
|
-
fragmentNames: readmeFragments,
|
|
108
|
-
variables,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
writeDocFromFragments({
|
|
112
|
-
targetRoot,
|
|
113
|
-
outputPath: path.join('docs', 'AI', 'PROJECT.md'),
|
|
114
|
-
fragmentsRoot,
|
|
115
|
-
docKey: 'AI_PROJECT',
|
|
116
|
-
fragmentNames: aiProjectFragments,
|
|
117
|
-
variables,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
writeDocFromFragments({
|
|
121
|
-
targetRoot,
|
|
122
|
-
outputPath: path.join('docs', 'AI', 'ARCHITECTURE.md'),
|
|
123
|
-
fragmentsRoot,
|
|
124
|
-
docKey: 'AI_ARCHITECTURE',
|
|
125
|
-
fragmentNames: aiArchitectureFragments,
|
|
126
|
-
variables,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getDatabaseLabel } from '../databases/index.mjs';
|
|
4
|
+
import { getFrontendLabel } from '../frameworks/index.mjs';
|
|
5
|
+
import { getProxyConfigPath } from '../infrastructure/proxy.mjs';
|
|
6
|
+
|
|
7
|
+
function renderTemplate(content, variables) {
|
|
8
|
+
return content.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key) => String(variables[key] ?? ''));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readFragment(fragmentsRoot, docKey, fragmentName, variables) {
|
|
12
|
+
const fragmentPath = path.join(fragmentsRoot, docKey, `${fragmentName}.md`);
|
|
13
|
+
if (!fs.existsSync(fragmentPath)) {
|
|
14
|
+
throw new Error(`Missing docs fragment: ${fragmentPath}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const raw = fs.readFileSync(fragmentPath, 'utf8').replace(/\r\n/g, '\n').trim();
|
|
18
|
+
return renderTemplate(raw, variables).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeDocFromFragments({
|
|
22
|
+
targetRoot,
|
|
23
|
+
outputPath,
|
|
24
|
+
fragmentsRoot,
|
|
25
|
+
docKey,
|
|
26
|
+
fragmentNames,
|
|
27
|
+
variables,
|
|
28
|
+
}) {
|
|
29
|
+
const fragments = fragmentNames
|
|
30
|
+
.map((fragmentName) => readFragment(fragmentsRoot, docKey, fragmentName, variables))
|
|
31
|
+
.filter((fragment) => fragment.length > 0);
|
|
32
|
+
|
|
33
|
+
const content = `${fragments
|
|
34
|
+
.join('\n\n')
|
|
35
|
+
.replace(/\n{2,}(?=- )/g, '\n')
|
|
36
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
37
|
+
.trimEnd()}\n`;
|
|
38
|
+
const absoluteOutputPath = path.join(targetRoot, outputPath);
|
|
39
|
+
fs.mkdirSync(path.dirname(absoluteOutputPath), { recursive: true });
|
|
40
|
+
fs.writeFileSync(absoluteOutputPath, content, 'utf8');
|
|
41
|
+
}
|
|
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');
|
|
57
|
+
readmeFragments.push(
|
|
58
|
+
options.proxy === 'none' ? '30_quick_start_docker_none' : '30_quick_start_docker',
|
|
59
|
+
);
|
|
60
|
+
if (options.proxy === 'caddy') {
|
|
61
|
+
readmeFragments.push('31_proxy_preset_caddy');
|
|
62
|
+
} else if (options.proxy === 'nginx') {
|
|
63
|
+
readmeFragments.push('31_proxy_preset_nginx');
|
|
64
|
+
} else {
|
|
65
|
+
readmeFragments.push('31_proxy_preset_none');
|
|
66
|
+
}
|
|
67
|
+
readmeFragments.push('32_prisma_container_start');
|
|
68
|
+
if (options.i18nEnabled) {
|
|
69
|
+
readmeFragments.push('40_i18n');
|
|
70
|
+
}
|
|
71
|
+
readmeFragments.push('90_next_steps');
|
|
72
|
+
|
|
73
|
+
const aiProjectFragments = ['00_title', '10_what_is', '20_structure_base'];
|
|
74
|
+
if (options.i18nEnabled) {
|
|
75
|
+
aiProjectFragments.push('21_structure_i18n');
|
|
76
|
+
}
|
|
77
|
+
aiProjectFragments.push('22_structure_docker', '23_structure_docs', '30_run_dev', '31_run_docker');
|
|
78
|
+
if (options.proxy === 'none') {
|
|
79
|
+
aiProjectFragments.push('32_proxy_notes_none');
|
|
80
|
+
} else {
|
|
81
|
+
aiProjectFragments.push('32_proxy_notes');
|
|
82
|
+
}
|
|
83
|
+
if (options.i18nEnabled) {
|
|
84
|
+
aiProjectFragments.push('33_i18n_notes');
|
|
85
|
+
}
|
|
86
|
+
aiProjectFragments.push('40_change_boundaries_base');
|
|
87
|
+
if (options.proxy !== 'none') {
|
|
88
|
+
aiProjectFragments.push('41_change_boundaries_docker');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const aiArchitectureFragments = ['00_title', '10_layout_base', '11_layout_infra'];
|
|
92
|
+
if (options.i18nEnabled) {
|
|
93
|
+
aiArchitectureFragments.push('12_layout_i18n_resources');
|
|
94
|
+
}
|
|
95
|
+
aiArchitectureFragments.push('20_env_base');
|
|
96
|
+
if (options.i18nEnabled) {
|
|
97
|
+
aiArchitectureFragments.push('21_env_i18n');
|
|
98
|
+
}
|
|
99
|
+
aiArchitectureFragments.push('30_default_db', '31_docker_runtime', '32_scope_freeze');
|
|
100
|
+
aiArchitectureFragments.push('40_docs_generation', '50_extension_points');
|
|
101
|
+
|
|
102
|
+
writeDocFromFragments({
|
|
103
|
+
targetRoot,
|
|
104
|
+
outputPath: 'README.md',
|
|
105
|
+
fragmentsRoot,
|
|
106
|
+
docKey: 'README',
|
|
107
|
+
fragmentNames: readmeFragments,
|
|
108
|
+
variables,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
writeDocFromFragments({
|
|
112
|
+
targetRoot,
|
|
113
|
+
outputPath: path.join('docs', 'AI', 'PROJECT.md'),
|
|
114
|
+
fragmentsRoot,
|
|
115
|
+
docKey: 'AI_PROJECT',
|
|
116
|
+
fragmentNames: aiProjectFragments,
|
|
117
|
+
variables,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
writeDocFromFragments({
|
|
121
|
+
targetRoot,
|
|
122
|
+
outputPath: path.join('docs', 'AI', 'ARCHITECTURE.md'),
|
|
123
|
+
fragmentsRoot,
|
|
124
|
+
docKey: 'AI_ARCHITECTURE',
|
|
125
|
+
fragmentNames: aiArchitectureFragments,
|
|
126
|
+
variables,
|
|
127
|
+
});
|
|
128
|
+
}
|
package/src/core/docs.test.mjs
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { generateDocs } from './docs.mjs';
|
|
8
|
-
|
|
9
|
-
function makeTempDir(prefix) {
|
|
10
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function readFile(filePath) {
|
|
14
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('generateDocs', () => {
|
|
18
|
-
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const packageRoot = path.resolve(thisDir, '..', '..');
|
|
20
|
-
|
|
21
|
-
it('generates docs for proxy=none without i18n section', () => {
|
|
22
|
-
const targetRoot = makeTempDir('forgeon-docs-off-');
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
generateDocs(
|
|
26
|
-
targetRoot,
|
|
27
|
-
{
|
|
28
|
-
frontend: 'react',
|
|
29
|
-
db: 'prisma',
|
|
30
|
-
dockerEnabled: true,
|
|
31
|
-
i18nEnabled: false,
|
|
32
|
-
proxy: 'none',
|
|
33
|
-
},
|
|
34
|
-
packageRoot,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const readme = readFile(path.join(targetRoot, 'README.md'));
|
|
38
|
-
const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
|
|
39
|
-
const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
|
|
40
|
-
|
|
41
|
-
assert.match(readme, /Docker\/infra: `enabled`/);
|
|
42
|
-
assert.match(readme, /Quick Start \(Docker\)/);
|
|
43
|
-
assert.match(readme, /Proxy Preset: none/);
|
|
44
|
-
assert.doesNotMatch(readme, /i18n Configuration/);
|
|
45
|
-
|
|
46
|
-
assert.match(projectDoc, /### Docker mode/);
|
|
47
|
-
assert.match(projectDoc, /Active proxy preset: `none`/);
|
|
48
|
-
assert.doesNotMatch(projectDoc, /packages\/i18n/);
|
|
49
|
-
|
|
50
|
-
assert.match(architectureDoc, /infra\/\*/);
|
|
51
|
-
assert.doesNotMatch(architectureDoc, /I18N_ENABLED/);
|
|
52
|
-
} finally {
|
|
53
|
-
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('generates docker and caddy notes when enabled', () => {
|
|
58
|
-
const targetRoot = makeTempDir('forgeon-docs-on-');
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
generateDocs(
|
|
62
|
-
targetRoot,
|
|
63
|
-
{
|
|
64
|
-
frontend: 'react',
|
|
65
|
-
db: 'prisma',
|
|
66
|
-
dockerEnabled: true,
|
|
67
|
-
i18nEnabled: true,
|
|
68
|
-
proxy: 'caddy',
|
|
69
|
-
},
|
|
70
|
-
packageRoot,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const readme = readFile(path.join(targetRoot, 'README.md'));
|
|
74
|
-
const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
|
|
75
|
-
const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
|
|
76
|
-
|
|
77
|
-
assert.match(readme, /Quick Start \(Docker\)/);
|
|
78
|
-
assert.match(readme, /Proxy Preset: Caddy/);
|
|
79
|
-
assert.match(readme, /i18n Configuration/);
|
|
80
|
-
|
|
81
|
-
assert.match(projectDoc, /`infra` - Docker Compose \(always\) \+ proxy preset \(`caddy`\)/);
|
|
82
|
-
assert.match(projectDoc, /Main proxy config: `infra\/caddy\/Caddyfile`/);
|
|
83
|
-
|
|
84
|
-
assert.match(architectureDoc, /infra\/\*/);
|
|
85
|
-
assert.match(architectureDoc, /I18N_ENABLED/);
|
|
86
|
-
assert.match(architectureDoc, /Active reverse proxy preset: `caddy`/);
|
|
87
|
-
} finally {
|
|
88
|
-
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
});
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { generateDocs } from './docs.mjs';
|
|
8
|
+
|
|
9
|
+
function makeTempDir(prefix) {
|
|
10
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readFile(filePath) {
|
|
14
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('generateDocs', () => {
|
|
18
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const packageRoot = path.resolve(thisDir, '..', '..');
|
|
20
|
+
|
|
21
|
+
it('generates docs for proxy=none without i18n section', () => {
|
|
22
|
+
const targetRoot = makeTempDir('forgeon-docs-off-');
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
generateDocs(
|
|
26
|
+
targetRoot,
|
|
27
|
+
{
|
|
28
|
+
frontend: 'react',
|
|
29
|
+
db: 'prisma',
|
|
30
|
+
dockerEnabled: true,
|
|
31
|
+
i18nEnabled: false,
|
|
32
|
+
proxy: 'none',
|
|
33
|
+
},
|
|
34
|
+
packageRoot,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const readme = readFile(path.join(targetRoot, 'README.md'));
|
|
38
|
+
const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
|
|
39
|
+
const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
|
|
40
|
+
|
|
41
|
+
assert.match(readme, /Docker\/infra: `enabled`/);
|
|
42
|
+
assert.match(readme, /Quick Start \(Docker\)/);
|
|
43
|
+
assert.match(readme, /Proxy Preset: none/);
|
|
44
|
+
assert.doesNotMatch(readme, /i18n Configuration/);
|
|
45
|
+
|
|
46
|
+
assert.match(projectDoc, /### Docker mode/);
|
|
47
|
+
assert.match(projectDoc, /Active proxy preset: `none`/);
|
|
48
|
+
assert.doesNotMatch(projectDoc, /packages\/i18n/);
|
|
49
|
+
|
|
50
|
+
assert.match(architectureDoc, /infra\/\*/);
|
|
51
|
+
assert.doesNotMatch(architectureDoc, /I18N_ENABLED/);
|
|
52
|
+
} finally {
|
|
53
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('generates docker and caddy notes when enabled', () => {
|
|
58
|
+
const targetRoot = makeTempDir('forgeon-docs-on-');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
generateDocs(
|
|
62
|
+
targetRoot,
|
|
63
|
+
{
|
|
64
|
+
frontend: 'react',
|
|
65
|
+
db: 'prisma',
|
|
66
|
+
dockerEnabled: true,
|
|
67
|
+
i18nEnabled: true,
|
|
68
|
+
proxy: 'caddy',
|
|
69
|
+
},
|
|
70
|
+
packageRoot,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const readme = readFile(path.join(targetRoot, 'README.md'));
|
|
74
|
+
const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
|
|
75
|
+
const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
|
|
76
|
+
|
|
77
|
+
assert.match(readme, /Quick Start \(Docker\)/);
|
|
78
|
+
assert.match(readme, /Proxy Preset: Caddy/);
|
|
79
|
+
assert.match(readme, /i18n Configuration/);
|
|
80
|
+
|
|
81
|
+
assert.match(projectDoc, /`infra` - Docker Compose \(always\) \+ proxy preset \(`caddy`\)/);
|
|
82
|
+
assert.match(projectDoc, /Main proxy config: `infra\/caddy\/Caddyfile`/);
|
|
83
|
+
|
|
84
|
+
assert.match(architectureDoc, /infra\/\*/);
|
|
85
|
+
assert.match(architectureDoc, /I18N_ENABLED/);
|
|
86
|
+
assert.match(architectureDoc, /Active reverse proxy preset: `caddy`/);
|
|
87
|
+
} finally {
|
|
88
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/core/install.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
|
|
3
|
-
export function runInstall(targetRoot) {
|
|
4
|
-
const pnpmCmd = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
5
|
-
const result = spawnSync(pnpmCmd, ['install'], {
|
|
6
|
-
cwd: targetRoot,
|
|
7
|
-
stdio: 'inherit',
|
|
8
|
-
shell: false,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
if (result.status !== 0) {
|
|
12
|
-
process.exit(result.status ?? 1);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function runInstall(targetRoot) {
|
|
4
|
+
const pnpmCmd = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
5
|
+
const result = spawnSync(pnpmCmd, ['install'], {
|
|
6
|
+
cwd: targetRoot,
|
|
7
|
+
stdio: 'inherit',
|
|
8
|
+
shell: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
if (result.status !== 0) {
|
|
12
|
+
process.exit(result.status ?? 1);
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/core/scaffold.mjs
CHANGED
|
@@ -3,55 +3,58 @@ import path from 'node:path';
|
|
|
3
3
|
import { copyRecursive, writeJson } from '../utils/fs.mjs';
|
|
4
4
|
import { toKebabCase } from '../utils/values.mjs';
|
|
5
5
|
import { applyI18nDisabled, applyProxyPreset, patchDockerEnvForI18n } from '../presets/index.mjs';
|
|
6
|
+
import { applyModulePreset } from '../modules/executor.mjs';
|
|
6
7
|
import { generateDocs } from './docs.mjs';
|
|
7
|
-
|
|
8
|
-
function writeApiEnvExample(targetRoot, i18nEnabled) {
|
|
9
|
-
const apiEnvExamplePath = path.join(targetRoot, 'apps', 'api', '.env.example');
|
|
10
|
-
const apiEnvLines = [
|
|
11
|
-
'PORT=3000',
|
|
12
|
-
'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app?schema=public',
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
if (i18nEnabled) {
|
|
16
|
-
apiEnvLines.push('I18N_ENABLED=true');
|
|
17
|
-
apiEnvLines.push('I18N_DEFAULT_LANG=en');
|
|
18
|
-
apiEnvLines.push('I18N_FALLBACK_LANG=en');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
fs.writeFileSync(apiEnvExamplePath, `${apiEnvLines.join('\n')}\n`, 'utf8');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function patchRootPackageJson(targetRoot, projectName) {
|
|
25
|
-
const rootPackageJsonPath = path.join(targetRoot, 'package.json');
|
|
26
|
-
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf8'));
|
|
27
|
-
rootPackageJson.name = toKebabCase(projectName);
|
|
28
|
-
|
|
29
|
-
if (rootPackageJson.scripts) {
|
|
30
|
-
delete rootPackageJson.scripts['create:forgeon'];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
writeJson(rootPackageJsonPath, rootPackageJson);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function scaffoldProject({
|
|
37
|
-
templateRoot,
|
|
38
|
-
packageRoot,
|
|
39
|
-
targetRoot,
|
|
40
|
-
projectName,
|
|
41
|
-
frontend,
|
|
42
|
-
db,
|
|
43
|
-
i18nEnabled,
|
|
44
|
-
proxy,
|
|
45
|
-
}) {
|
|
8
|
+
|
|
9
|
+
function writeApiEnvExample(targetRoot, i18nEnabled) {
|
|
10
|
+
const apiEnvExamplePath = path.join(targetRoot, 'apps', 'api', '.env.example');
|
|
11
|
+
const apiEnvLines = [
|
|
12
|
+
'PORT=3000',
|
|
13
|
+
'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app?schema=public',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
if (i18nEnabled) {
|
|
17
|
+
apiEnvLines.push('I18N_ENABLED=true');
|
|
18
|
+
apiEnvLines.push('I18N_DEFAULT_LANG=en');
|
|
19
|
+
apiEnvLines.push('I18N_FALLBACK_LANG=en');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fs.writeFileSync(apiEnvExamplePath, `${apiEnvLines.join('\n')}\n`, 'utf8');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function patchRootPackageJson(targetRoot, projectName) {
|
|
26
|
+
const rootPackageJsonPath = path.join(targetRoot, 'package.json');
|
|
27
|
+
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf8'));
|
|
28
|
+
rootPackageJson.name = toKebabCase(projectName);
|
|
29
|
+
|
|
30
|
+
if (rootPackageJson.scripts) {
|
|
31
|
+
delete rootPackageJson.scripts['create:forgeon'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
writeJson(rootPackageJsonPath, rootPackageJson);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function scaffoldProject({
|
|
38
|
+
templateRoot,
|
|
39
|
+
packageRoot,
|
|
40
|
+
targetRoot,
|
|
41
|
+
projectName,
|
|
42
|
+
frontend,
|
|
43
|
+
db,
|
|
44
|
+
i18nEnabled,
|
|
45
|
+
proxy,
|
|
46
|
+
}) {
|
|
46
47
|
copyRecursive(templateRoot, targetRoot);
|
|
47
48
|
patchRootPackageJson(targetRoot, projectName);
|
|
48
49
|
applyProxyPreset(targetRoot, proxy);
|
|
49
|
-
patchDockerEnvForI18n(targetRoot, i18nEnabled);
|
|
50
50
|
|
|
51
|
-
if (
|
|
51
|
+
if (i18nEnabled) {
|
|
52
|
+
applyModulePreset({ moduleId: 'i18n', targetRoot, packageRoot });
|
|
53
|
+
} else {
|
|
54
|
+
patchDockerEnvForI18n(targetRoot, i18nEnabled);
|
|
52
55
|
applyI18nDisabled(targetRoot);
|
|
53
56
|
}
|
|
54
|
-
|
|
55
|
-
writeApiEnvExample(targetRoot, i18nEnabled);
|
|
56
|
-
generateDocs(targetRoot, { frontend, db, dockerEnabled: true, i18nEnabled, proxy }, packageRoot);
|
|
57
|
-
}
|
|
57
|
+
|
|
58
|
+
writeApiEnvExample(targetRoot, i18nEnabled);
|
|
59
|
+
generateDocs(targetRoot, { frontend, db, dockerEnabled: true, i18nEnabled, proxy }, packageRoot);
|
|
60
|
+
}
|
package/src/core/validate.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ensureDatabaseSupported } from '../databases/index.mjs';
|
|
2
|
-
import { ensureFrontendSupported } from '../frameworks/index.mjs';
|
|
3
|
-
import { ensureProxySupported } from '../infrastructure/proxy.mjs';
|
|
4
|
-
|
|
5
|
-
export function validatePresetSupport({ frontend, db, dockerEnabled, proxy }) {
|
|
6
|
-
ensureFrontendSupported(frontend);
|
|
7
|
-
ensureDatabaseSupported(db);
|
|
8
|
-
|
|
9
|
-
if (dockerEnabled) {
|
|
10
|
-
ensureProxySupported(proxy);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
1
|
+
import { ensureDatabaseSupported } from '../databases/index.mjs';
|
|
2
|
+
import { ensureFrontendSupported } from '../frameworks/index.mjs';
|
|
3
|
+
import { ensureProxySupported } from '../infrastructure/proxy.mjs';
|
|
4
|
+
|
|
5
|
+
export function validatePresetSupport({ frontend, db, dockerEnabled, proxy }) {
|
|
6
|
+
ensureFrontendSupported(frontend);
|
|
7
|
+
ensureDatabaseSupported(db);
|
|
8
|
+
|
|
9
|
+
if (dockerEnabled) {
|
|
10
|
+
ensureProxySupported(proxy);
|
|
11
|
+
}
|
|
12
|
+
}
|