create-forgeon 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -324,6 +324,11 @@ function patchWebApp(targetRoot) {
324
324
  }
325
325
 
326
326
  let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
327
+ content = content
328
+ .replace(/^\s*\{\/\* forgeon:probes:actions:start \*\/\}\r?\n?/gm, '')
329
+ .replace(/^\s*\{\/\* forgeon:probes:actions:end \*\/\}\r?\n?/gm, '')
330
+ .replace(/^\s*\{\/\* forgeon:probes:results:start \*\/\}\r?\n?/gm, '')
331
+ .replace(/^\s*\{\/\* forgeon:probes:results:end \*\/\}\r?\n?/gm, '');
327
332
 
328
333
  if (!content.includes('dbProbeResult')) {
329
334
  const stateAnchor = ' const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);';
@@ -337,23 +342,29 @@ function patchWebApp(targetRoot) {
337
342
  }
338
343
 
339
344
  if (!content.includes('Check database (create user)')) {
340
- const buttonAnchor = " <button onClick={() => runProbe(setValidationProbeResult, '/api/health/validation')>";
341
- const buttonAnchorI18n = " <button onClick={() => runProbe(setValidationProbeResult, '/health/validation')>";
342
- const dbButton = content.includes(buttonAnchorI18n)
345
+ const dbButton = content.includes("runProbe(setHealthResult, '/health')")
343
346
  ? " <button onClick={() => runProbe(setDbProbeResult, '/health/db', { method: 'POST' })}>\n Check database (create user)\n </button>"
344
347
  : " <button onClick={() => runProbe(setDbProbeResult, '/api/health/db', { method: 'POST' })}>\n Check database (create user)\n </button>";
345
348
 
346
- if (content.includes(buttonAnchor)) {
347
- content = ensureLineAfter(content, buttonAnchor, dbButton);
348
- } else if (content.includes(buttonAnchorI18n)) {
349
- content = ensureLineAfter(content, buttonAnchorI18n, dbButton);
349
+ const actionsStart = content.indexOf('<div className="actions">');
350
+ if (actionsStart >= 0) {
351
+ const actionsEnd = content.indexOf('\n </div>', actionsStart);
352
+ if (actionsEnd >= 0) {
353
+ content = `${content.slice(0, actionsEnd)}\n${dbButton}${content.slice(actionsEnd)}`;
354
+ }
350
355
  }
351
356
  }
352
357
 
353
358
  if (!content.includes("{renderResult('DB probe response', dbProbeResult)}")) {
354
- const resultAnchor = "{renderResult('Validation probe response', validationProbeResult)}";
355
- if (content.includes(resultAnchor)) {
356
- content = ensureLineAfter(content, resultAnchor, " {renderResult('DB probe response', dbProbeResult)}");
359
+ const dbResultLine = " {renderResult('DB probe response', dbProbeResult)}";
360
+ const networkLine = ' {networkError ? <p className="error">{networkError}</p> : null}';
361
+ if (content.includes(networkLine)) {
362
+ content = content.replace(networkLine, `${dbResultLine}\n${networkLine}`);
363
+ } else {
364
+ const resultAnchor = "{renderResult('Validation probe response', validationProbeResult)}";
365
+ if (content.includes(resultAnchor)) {
366
+ content = ensureLineAfter(content, resultAnchor, dbResultLine);
367
+ }
357
368
  }
358
369
  }
359
370
 
@@ -2,9 +2,6 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { copyRecursive, writeJson } from '../utils/fs.mjs';
4
4
 
5
- const JWT_README_START = '<!-- forgeon:jwt-auth:start -->';
6
- const JWT_README_END = '<!-- forgeon:jwt-auth:end -->';
7
-
8
5
  function copyFromPreset(packageRoot, targetRoot, relativePath) {
9
6
  const source = path.join(packageRoot, 'templates', 'module-presets', 'jwt-auth', relativePath);
10
7
  if (!fs.existsSync(source)) {
@@ -356,30 +353,58 @@ function patchWebApp(targetRoot) {
356
353
  }
357
354
 
358
355
  let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
356
+ content = content
357
+ .replace(/^\s*\{\/\* forgeon:probes:actions:start \*\/\}\r?\n?/gm, '')
358
+ .replace(/^\s*\{\/\* forgeon:probes:actions:end \*\/\}\r?\n?/gm, '')
359
+ .replace(/^\s*\{\/\* forgeon:probes:results:start \*\/\}\r?\n?/gm, '')
360
+ .replace(/^\s*\{\/\* forgeon:probes:results:end \*\/\}\r?\n?/gm, '');
361
+
359
362
  if (!content.includes('authProbeResult')) {
360
- content = content.replace(
361
- ' const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);',
362
- ` const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);
363
+ if (content.includes(' const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);')) {
364
+ content = content.replace(
365
+ ' const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);',
366
+ ` const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);
363
367
  const [authProbeResult, setAuthProbeResult] = useState<ProbeResult | null>(null);`,
364
- );
368
+ );
369
+ } else if (content.includes(' const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);')) {
370
+ content = content.replace(
371
+ ' const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);',
372
+ ` const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);
373
+ const [authProbeResult, setAuthProbeResult] = useState<ProbeResult | null>(null);`,
374
+ );
375
+ }
365
376
  }
366
377
 
367
378
  if (!content.includes('Check JWT auth probe')) {
368
379
  const path = content.includes("runProbe(setHealthResult, '/health')") ? '/health/auth' : '/api/health/auth';
369
- content = content.replace(
370
- /<button onClick=\{\(\) => runProbe\(setErrorProbeResult,[\s\S]*?<\/button>/m,
371
- (match) =>
372
- `${match}
373
- <button onClick={() => runProbe(setAuthProbeResult, '${path}')}>Check JWT auth probe</button>`,
374
- );
380
+ const authButton = ` <button onClick={() => runProbe(setAuthProbeResult, '${path}')}>Check JWT auth probe</button>`;
381
+ const actionsStart = content.indexOf('<div className="actions">');
382
+ if (actionsStart >= 0) {
383
+ const actionsEnd = content.indexOf('\n </div>', actionsStart);
384
+ if (actionsEnd >= 0) {
385
+ content = `${content.slice(0, actionsEnd)}\n${authButton}${content.slice(actionsEnd)}`;
386
+ }
387
+ }
375
388
  }
376
389
 
377
390
  if (!content.includes("renderResult('Auth probe response', authProbeResult)")) {
378
- content = content.replace(
379
- "{renderResult('DB probe response', dbProbeResult)}",
380
- `{renderResult('DB probe response', dbProbeResult)}
391
+ const authResultLine = " {renderResult('Auth probe response', authProbeResult)}";
392
+ const networkLine = ' {networkError ? <p className="error">{networkError}</p> : null}';
393
+ if (content.includes(networkLine)) {
394
+ content = content.replace(networkLine, `${authResultLine}\n${networkLine}`);
395
+ } else if (content.includes("{renderResult('DB probe response', dbProbeResult)}")) {
396
+ content = content.replace(
397
+ "{renderResult('DB probe response', dbProbeResult)}",
398
+ `{renderResult('DB probe response', dbProbeResult)}
381
399
  {renderResult('Auth probe response', authProbeResult)}`,
382
- );
400
+ );
401
+ } else if (content.includes("{renderResult('Validation probe response', validationProbeResult)}")) {
402
+ content = content.replace(
403
+ "{renderResult('Validation probe response', validationProbeResult)}",
404
+ `{renderResult('Validation probe response', validationProbeResult)}
405
+ {renderResult('Auth probe response', authProbeResult)}`,
406
+ );
407
+ }
383
408
  }
384
409
 
385
410
  fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
@@ -480,8 +505,7 @@ function patchReadme(targetRoot, dbAdapter) {
480
505
  1. install a DB module first (for now: \`create-forgeon add db-prisma --project .\`);
481
506
  2. run \`create-forgeon add jwt-auth --project .\` again to auto-wire the adapter.`;
482
507
 
483
- const section = `${JWT_README_START}
484
- ## JWT Auth Module
508
+ const section = `## JWT Auth Module
485
509
 
486
510
  The jwt-auth add-module provides:
487
511
  - \`@forgeon/auth-contracts\` shared auth routes/types/error codes
@@ -501,13 +525,19 @@ Default routes:
501
525
  - \`POST /api/auth/login\`
502
526
  - \`POST /api/auth/refresh\`
503
527
  - \`POST /api/auth/logout\`
504
- - \`GET /api/auth/me\`
505
- ${JWT_README_END}`;
528
+ - \`GET /api/auth/me\``;
506
529
 
507
530
  let content = fs.readFileSync(readmePath, 'utf8').replace(/\r\n/g, '\n');
508
- const sectionPattern = new RegExp(`${JWT_README_START}[\\s\\S]*?${JWT_README_END}`, 'm');
509
- if (sectionPattern.test(content)) {
510
- content = content.replace(sectionPattern, section);
531
+ const sectionHeading = '## JWT Auth Module';
532
+ if (content.includes(sectionHeading)) {
533
+ const start = content.indexOf(sectionHeading);
534
+ const tail = content.slice(start + sectionHeading.length);
535
+ const nextHeadingMatch = tail.match(/\n##\s+/);
536
+ const end =
537
+ nextHeadingMatch && nextHeadingMatch.index !== undefined
538
+ ? start + sectionHeading.length + nextHeadingMatch.index + 1
539
+ : content.length;
540
+ content = `${content.slice(0, start)}${section}\n\n${content.slice(end).replace(/^\n+/, '')}`;
511
541
  } else if (content.includes('## Prisma In Docker Start')) {
512
542
  content = content.replace('## Prisma In Docker Start', `${section}\n\n## Prisma In Docker Start`);
513
543
  } else {
@@ -566,6 +596,14 @@ export function applyJwtAuthModule({ packageRoot, targetRoot }) {
566
596
  copyFromPreset(packageRoot, targetRoot, path.join('packages', 'auth-contracts'));
567
597
  copyFromPreset(packageRoot, targetRoot, path.join('packages', 'auth-api'));
568
598
 
599
+ const swaggerPackagePath = path.join(targetRoot, 'packages', 'swagger', 'package.json');
600
+ const authApiPackagePath = path.join(targetRoot, 'packages', 'auth-api', 'package.json');
601
+ if (fs.existsSync(swaggerPackagePath) && fs.existsSync(authApiPackagePath)) {
602
+ const authApiPackage = JSON.parse(fs.readFileSync(authApiPackagePath, 'utf8'));
603
+ ensureDependency(authApiPackage, '@nestjs/swagger', '^11.2.0');
604
+ writeJson(authApiPackagePath, authApiPackage);
605
+ }
606
+
569
607
  if (supportsPrismaStore) {
570
608
  copyFromPreset(
571
609
  packageRoot,
@@ -23,3 +23,6 @@ If a module can be validated through a safe API call, it must provide:
23
23
  - If probe writes data, it must use clearly marked probe/test records.
24
24
  - Probe should not require hidden setup beyond documented env/dependencies.
25
25
  - `create-forgeon add <module>` must wire both API probe and web probe UI when feasible.
26
+ - Web probes should be appended to the existing probe UI structure in `apps/web/src/App.tsx`:
27
+ - add new action button at the end of `<div className="actions">`
28
+ - add new result block before the `networkError` render block
@@ -54,8 +54,14 @@ function syncJwtSwagger({ rootDir, changedFiles }) {
54
54
  );
55
55
  const loginDtoPath = path.join(rootDir, 'packages', 'auth-api', 'src', 'dto', 'login.dto.ts');
56
56
  const refreshDtoPath = path.join(rootDir, 'packages', 'auth-api', 'src', 'dto', 'refresh.dto.ts');
57
-
58
- if (!fs.existsSync(controllerPath) || !fs.existsSync(loginDtoPath) || !fs.existsSync(refreshDtoPath)) {
57
+ const authApiPackagePath = path.join(rootDir, 'packages', 'auth-api', 'package.json');
58
+
59
+ if (
60
+ !fs.existsSync(controllerPath) ||
61
+ !fs.existsSync(loginDtoPath) ||
62
+ !fs.existsSync(refreshDtoPath) ||
63
+ !fs.existsSync(authApiPackagePath)
64
+ ) {
59
65
  return { applied: false, reason: 'jwt-auth source files are missing' };
60
66
  }
61
67
 
@@ -153,6 +159,16 @@ function syncJwtSwagger({ rootDir, changedFiles }) {
153
159
  changedFiles.add(loginDtoPath);
154
160
  changedFiles.add(refreshDtoPath);
155
161
 
162
+ const authApiPackage = JSON.parse(fs.readFileSync(authApiPackagePath, 'utf8'));
163
+ if (!authApiPackage.dependencies) {
164
+ authApiPackage.dependencies = {};
165
+ }
166
+ if (!authApiPackage.dependencies['@nestjs/swagger']) {
167
+ authApiPackage.dependencies['@nestjs/swagger'] = '^11.2.0';
168
+ fs.writeFileSync(authApiPackagePath, `${JSON.stringify(authApiPackage, null, 2)}\n`, 'utf8');
169
+ changedFiles.add(authApiPackagePath);
170
+ }
171
+
156
172
  return { applied: true };
157
173
  }
158
174