create-forgeon 0.2.8 → 0.2.10

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 CHANGED
@@ -37,4 +37,4 @@ pnpm forgeon:sync-integrations
37
37
  - `add i18n` is implemented and applies backend/frontend i18n wiring.
38
38
  - `add jwt-auth` is implemented and auto-detects DB adapter support for refresh-token persistence.
39
39
  - Integration sync is bundled by default and runs after `add` commands (best-effort).
40
- - Planned modules write docs notes under `docs/AI/MODULES/`.
40
+ - Module notes are written under `modules/<module-id>/README.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
package/src/core/docs.mjs CHANGED
@@ -1,10 +1,9 @@
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) {
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
+
6
+ function renderTemplate(content, variables) {
8
7
  return content.replace(/\{\{([A-Z0-9_]+)\}\}/g, (_, key) => String(variables[key] ?? ''));
9
8
  }
10
9
 
@@ -50,7 +49,6 @@ export function generateDocs(targetRoot, options, packageRoot) {
50
49
  DB_PRISMA_STATUS: options.dbPrismaEnabled ? 'enabled' : 'disabled',
51
50
  DOCKER_STATUS: 'enabled',
52
51
  PROXY_LABEL: options.proxy,
53
- PROXY_CONFIG_PATH: getProxyConfigPath(options.proxy),
54
52
  };
55
53
 
56
54
  const readmeFragments = ['00_title', '10_stack', '20_quick_start_dev_intro'];
@@ -78,76 +76,13 @@ export function generateDocs(targetRoot, options, packageRoot) {
78
76
  }
79
77
  readmeFragments.push('41_error_handling');
80
78
  readmeFragments.push('90_next_steps');
81
-
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');
90
- }
91
- aiProjectFragments.push('22_structure_docker', '23_structure_docs', '30_run_dev', '31_run_docker');
92
- if (options.proxy === 'none') {
93
- aiProjectFragments.push('32_proxy_notes_none');
94
- } else {
95
- aiProjectFragments.push('32_proxy_notes');
96
- }
97
- if (options.i18nEnabled) {
98
- aiProjectFragments.push('33_i18n_notes');
99
- }
100
- aiProjectFragments.push('34_error_handling');
101
- aiProjectFragments.push('40_change_boundaries_base');
102
- if (options.proxy !== 'none') {
103
- aiProjectFragments.push('41_change_boundaries_docker');
104
- }
105
-
106
- const aiArchitectureFragments = ['00_title', '10_layout_base', '11_layout_infra'];
107
- if (options.i18nEnabled) {
108
- aiArchitectureFragments.push('12_layout_i18n_resources');
109
- }
110
- aiArchitectureFragments.push('20_env_base');
111
- if (options.dbPrismaEnabled) {
112
- aiArchitectureFragments.push('20b_env_db_prisma');
113
- }
114
- if (options.i18nEnabled) {
115
- aiArchitectureFragments.push('21_env_i18n');
116
- }
117
- aiArchitectureFragments.push('22_ts_module_policy');
118
- aiArchitectureFragments.push('23_error_handling');
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');
125
- aiArchitectureFragments.push('40_docs_generation', '50_extension_points');
126
-
127
- writeDocFromFragments({
128
- targetRoot,
79
+
80
+ writeDocFromFragments({
81
+ targetRoot,
129
82
  outputPath: 'README.md',
130
83
  fragmentsRoot,
131
- docKey: 'README',
132
- fragmentNames: readmeFragments,
133
- variables,
134
- });
135
-
136
- writeDocFromFragments({
137
- targetRoot,
138
- outputPath: path.join('docs', 'AI', 'PROJECT.md'),
139
- fragmentsRoot,
140
- docKey: 'AI_PROJECT',
141
- fragmentNames: aiProjectFragments,
142
- variables,
143
- });
144
-
145
- writeDocFromFragments({
146
- targetRoot,
147
- outputPath: path.join('docs', 'AI', 'ARCHITECTURE.md'),
148
- fragmentsRoot,
149
- docKey: 'AI_ARCHITECTURE',
150
- fragmentNames: aiArchitectureFragments,
151
- variables,
152
- });
153
- }
84
+ docKey: 'README',
85
+ fragmentNames: readmeFragments,
86
+ variables,
87
+ });
88
+ }
@@ -18,7 +18,7 @@ describe('generateDocs', () => {
18
18
  const thisDir = path.dirname(fileURLToPath(import.meta.url));
19
19
  const packageRoot = path.resolve(thisDir, '..', '..');
20
20
 
21
- it('generates docs for proxy=none without i18n section', () => {
21
+ it('generates docs for proxy=none without i18n section', () => {
22
22
  const targetRoot = makeTempDir('forgeon-docs-off-');
23
23
 
24
24
  try {
@@ -34,31 +34,18 @@ describe('generateDocs', () => {
34
34
  },
35
35
  packageRoot,
36
36
  );
37
-
38
- const readme = readFile(path.join(targetRoot, 'README.md'));
39
- const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
40
- const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
41
-
37
+
38
+ const readme = readFile(path.join(targetRoot, 'README.md'));
39
+
42
40
  assert.match(readme, /db-prisma`: `disabled`/);
43
41
  assert.match(readme, /No DB module is enabled by default/);
44
42
  assert.match(readme, /Quick Start \(Docker\)/);
45
43
  assert.match(readme, /Proxy Preset: none/);
46
44
  assert.match(readme, /Error Handling \(`core-errors`\)/);
45
+ assert.match(readme, /Module notes index: `modules\/README\.md`/);
47
46
  assert.doesNotMatch(readme, /i18n Configuration/);
48
47
  assert.doesNotMatch(readme, /Prisma In Container Start/);
49
-
50
- assert.match(projectDoc, /### Docker mode/);
51
- assert.match(projectDoc, /Active proxy preset: `none`/);
52
- assert.match(projectDoc, /CoreErrorsModule/);
53
- assert.doesNotMatch(projectDoc, /packages\/i18n/);
54
-
55
- assert.match(architectureDoc, /generated without `db-prisma`/);
56
- assert.doesNotMatch(architectureDoc, /I18N_ENABLED/);
57
- assert.match(architectureDoc, /API_PREFIX/);
58
- assert.match(architectureDoc, /Config Strategy/);
59
- assert.match(architectureDoc, /TypeScript Module Policy/);
60
- assert.match(architectureDoc, /tsconfig\.base\.esm\.json/);
61
- assert.doesNotMatch(architectureDoc, /DbPrismaModule/);
48
+ assert.equal(fs.existsSync(path.join(targetRoot, 'docs')), false);
62
49
  } finally {
63
50
  fs.rmSync(targetRoot, { recursive: true, force: true });
64
51
  }
@@ -80,29 +67,17 @@ describe('generateDocs', () => {
80
67
  },
81
68
  packageRoot,
82
69
  );
83
-
84
- const readme = readFile(path.join(targetRoot, 'README.md'));
85
- const projectDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'PROJECT.md'));
86
- const architectureDoc = readFile(path.join(targetRoot, 'docs', 'AI', 'ARCHITECTURE.md'));
87
-
70
+
71
+ const readme = readFile(path.join(targetRoot, 'README.md'));
72
+
88
73
  assert.match(readme, /Quick Start \(Docker\)/);
89
74
  assert.match(readme, /Proxy Preset: Caddy/);
90
75
  assert.match(readme, /i18n Configuration/);
91
76
  assert.match(readme, /db-prisma`: `enabled`/);
92
77
  assert.match(readme, /Prisma In Container Start/);
93
78
  assert.match(readme, /Error Handling \(`core-errors`\)/);
94
-
95
- assert.match(projectDoc, /`infra` - Docker Compose \(always\) \+ proxy preset \(`caddy`\)/);
96
- assert.match(projectDoc, /Main proxy config: `infra\/caddy\/Caddyfile`/);
97
- assert.match(projectDoc, /CoreExceptionFilter/);
98
-
99
- assert.match(architectureDoc, /infra\/\*/);
100
- assert.match(architectureDoc, /I18N_DEFAULT_LANG/);
101
- assert.doesNotMatch(architectureDoc, /I18N_ENABLED/);
102
- assert.match(architectureDoc, /Active reverse proxy preset: `caddy`/);
103
- assert.match(architectureDoc, /TypeScript Module Policy/);
104
- assert.match(architectureDoc, /tsconfig\.base\.node\.json/);
105
- assert.match(architectureDoc, /DbPrismaModule/);
79
+ assert.match(readme, /Module-specific notes: `modules\/<module-id>\/README\.md`/);
80
+ assert.equal(fs.existsSync(path.join(targetRoot, 'docs')), false);
106
81
  } finally {
107
82
  fs.rmSync(targetRoot, { recursive: true, force: true });
108
83
  }
@@ -38,7 +38,7 @@ function patchRootPackageJson(targetRoot, projectName) {
38
38
  writeJson(rootPackageJsonPath, rootPackageJson);
39
39
  }
40
40
 
41
- export function scaffoldProject({
41
+ export function scaffoldProject({
42
42
  templateRoot,
43
43
  packageRoot,
44
44
  targetRoot,
@@ -50,6 +50,10 @@ export function scaffoldProject({
50
50
  proxy,
51
51
  }) {
52
52
  copyRecursive(templateRoot, targetRoot);
53
+ const generatedDocsPath = path.join(targetRoot, 'docs');
54
+ if (fs.existsSync(generatedDocsPath)) {
55
+ fs.rmSync(generatedDocsPath, { recursive: true, force: true });
56
+ }
53
57
  patchRootPackageJson(targetRoot, projectName);
54
58
  applyProxyPreset(targetRoot, proxy);
55
59
 
@@ -114,5 +114,5 @@ export async function runIntegrationFlow({
114
114
 
115
115
  export function printModuleAdded(moduleId, docsPath) {
116
116
  console.log(colorize('green', `✔ Module added: ${moduleId}`));
117
- console.log(`- docs: ${docsPath}`);
117
+ console.log(`- readme: ${docsPath}`);
118
118
  }
@@ -20,25 +20,25 @@ function readModuleFragment(packageRoot, moduleId, fragmentName, variables) {
20
20
  return renderTemplate(raw, variables).trim();
21
21
  }
22
22
 
23
- function ensureModuleIndex(targetRoot) {
24
- const indexPath = path.join(targetRoot, 'docs', 'AI', 'MODULES', 'README.md');
25
- if (!fs.existsSync(indexPath)) {
26
- fs.mkdirSync(path.dirname(indexPath), { recursive: true });
27
- fs.writeFileSync(
28
- indexPath,
29
- '# MODULES\n\nGenerated notes for module presets added via `create-forgeon add`.\n',
30
- 'utf8',
31
- );
32
- }
33
- return indexPath;
34
- }
35
-
36
- function updateModuleIndex(indexPath, preset) {
37
- const relativePath = `${preset.id}.md`;
38
- const nextLine = `- \`${preset.id}\` - ${preset.label} (${preset.implemented ? 'implemented' : 'planned'})`;
39
- const current = fs.readFileSync(indexPath, 'utf8').replace(/\r\n/g, '\n');
40
-
41
- if (current.includes(`\`${preset.id}\``)) {
23
+ function ensureModuleIndex(targetRoot) {
24
+ const indexPath = path.join(targetRoot, 'modules', 'README.md');
25
+ if (!fs.existsSync(indexPath)) {
26
+ fs.mkdirSync(path.dirname(indexPath), { recursive: true });
27
+ fs.writeFileSync(
28
+ indexPath,
29
+ '# Modules\n\nUser-facing notes for modules added via `create-forgeon add`.\n',
30
+ 'utf8',
31
+ );
32
+ }
33
+ return indexPath;
34
+ }
35
+
36
+ function updateModuleIndex(indexPath, preset) {
37
+ const relativePath = `./${preset.id}/README.md`;
38
+ const nextLine = `- [\`${preset.id}\`](${relativePath}) - ${preset.label} (${preset.implemented ? 'implemented' : 'planned'})`;
39
+ const current = fs.readFileSync(indexPath, 'utf8').replace(/\r\n/g, '\n');
40
+
41
+ if (current.includes(`\`${preset.id}\``)) {
42
42
  return;
43
43
  }
44
44
 
@@ -46,7 +46,7 @@ function updateModuleIndex(indexPath, preset) {
46
46
  fs.writeFileSync(indexPath, content, 'utf8');
47
47
  }
48
48
 
49
- export function writeModuleDocs({ packageRoot, targetRoot, preset }) {
49
+ export function writeModuleDocs({ packageRoot, targetRoot, preset }) {
50
50
  const variables = {
51
51
  MODULE_ID: preset.id,
52
52
  MODULE_LABEL: preset.label,
@@ -59,9 +59,9 @@ export function writeModuleDocs({ packageRoot, targetRoot, preset }) {
59
59
  .map((fragmentName) => readModuleFragment(packageRoot, preset.id, fragmentName, variables))
60
60
  .filter(Boolean);
61
61
 
62
- const outputPath = path.join(targetRoot, 'docs', 'AI', 'MODULES', `${preset.id}.md`);
63
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
64
- fs.writeFileSync(outputPath, `${sections.join('\n\n').trimEnd()}\n`, 'utf8');
62
+ const outputPath = path.join(targetRoot, 'modules', preset.id, 'README.md');
63
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
64
+ fs.writeFileSync(outputPath, `${sections.join('\n\n').trimEnd()}\n`, 'utf8');
65
65
 
66
66
  const indexPath = ensureModuleIndex(targetRoot);
67
67
  updateModuleIndex(indexPath, preset);
@@ -57,7 +57,7 @@ export function addModule({ moduleId, targetRoot, packageRoot, writeDocs = true
57
57
  targetRoot,
58
58
  preset,
59
59
  })
60
- : path.join(targetRoot, 'docs', 'AI', 'MODULES', `${preset.id}.md`);
60
+ : path.join(targetRoot, 'modules', preset.id, 'README.md');
61
61
 
62
62
  return {
63
63
  preset,
@@ -289,11 +289,13 @@ describe('addModule', () => {
289
289
  packageRoot,
290
290
  });
291
291
 
292
- assert.equal(result.applied, false);
293
- assert.match(result.message, /planned/);
294
- assert.equal(fs.existsSync(result.docsPath), true);
295
-
296
- const note = fs.readFileSync(result.docsPath, 'utf8');
292
+ assert.equal(result.applied, false);
293
+ assert.match(result.message, /planned/);
294
+ assert.equal(fs.existsSync(result.docsPath), true);
295
+ assert.match(result.docsPath, /modules[\\/].+[\\/]README\.md$/);
296
+ assert.equal(fs.existsSync(path.join(targetRoot, 'modules', 'README.md')), true);
297
+
298
+ const note = fs.readFileSync(result.docsPath, 'utf8');
297
299
  assert.match(note, /Queue Worker/);
298
300
  assert.match(note, /Status: planned/);
299
301
  } finally {
@@ -337,6 +339,8 @@ describe('addModule', () => {
337
339
  proxy: 'caddy',
338
340
  });
339
341
 
342
+ assert.equal(fs.existsSync(path.join(projectRoot, 'docs')), false);
343
+
340
344
  const result = addModule({
341
345
  moduleId: 'i18n',
342
346
  targetRoot: projectRoot,
@@ -1,9 +1,11 @@
1
- ## Docs Generation Pipeline
2
-
3
- Project docs are assembled from markdown fragments in:
4
-
5
- - `packages/create-forgeon/templates/docs-fragments/README`
6
- - `packages/create-forgeon/templates/docs-fragments/AI_PROJECT`
7
- - `packages/create-forgeon/templates/docs-fragments/AI_ARCHITECTURE`
8
-
9
- During scaffold generation, the CLI selects fragments based on chosen flags and writes final docs into project root and `docs/AI`.
1
+ ## Docs Generation Pipeline
2
+
3
+ Project docs are assembled from markdown fragments in:
4
+
5
+ - `packages/create-forgeon/templates/docs-fragments/README`
6
+ - `packages/create-forgeon/templates/docs-fragments/AI_PROJECT`
7
+ - `packages/create-forgeon/templates/docs-fragments/AI_ARCHITECTURE`
8
+
9
+ During scaffold generation, the CLI currently writes the generated project `README.md` from these fragments.
10
+
11
+ Internal architecture fragments remain in the Forgeon repository as generator source material and are not emitted into generated projects by default.
@@ -1,7 +1,6 @@
1
- ## Next Steps
2
-
3
- - Backend entrypoint: `apps/api/src/main.ts`
4
- - Frontend entrypoint: `apps/web/src/main.tsx`
5
- - Project docs index: `docs/README.md`
6
- - AI workflow docs: `docs/AI/*`
7
- - Module contract spec: `docs/AI/MODULE_SPEC.md`
1
+ ## Next Steps
2
+
3
+ - Backend entrypoint: `apps/api/src/main.ts`
4
+ - Frontend entrypoint: `apps/web/src/main.tsx`
5
+ - Module notes index: `modules/README.md`
6
+ - Module-specific notes: `modules/<module-id>/README.md`