create-forgeon 0.1.38 → 0.2.1
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 +134 -32
- package/src/modules/executor.test.mjs +44 -13
- package/src/modules/i18n.mjs +11 -28
- 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/{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
|
@@ -2,10 +2,10 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { copyRecursive, writeJson } from '../utils/fs.mjs';
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
const source = path.join(packageRoot, 'templates', '
|
|
5
|
+
function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
6
|
+
const source = path.join(packageRoot, 'templates', 'module-presets', 'db-prisma', relativePath);
|
|
7
7
|
if (!fs.existsSync(source)) {
|
|
8
|
-
throw new Error(`Missing db-prisma
|
|
8
|
+
throw new Error(`Missing db-prisma preset template: ${source}`);
|
|
9
9
|
}
|
|
10
10
|
const destination = path.join(targetRoot, relativePath);
|
|
11
11
|
copyRecursive(source, destination);
|
|
@@ -84,6 +84,26 @@ function ensureLineBefore(content, anchorLine, lineToInsert) {
|
|
|
84
84
|
return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function ensureNestCommonImport(content, importName) {
|
|
88
|
+
const pattern = /import\s*\{([^}]*)\}\s*from '@nestjs\/common';/m;
|
|
89
|
+
const match = content.match(pattern);
|
|
90
|
+
if (!match) {
|
|
91
|
+
return `import { ${importName} } from '@nestjs/common';\n${content}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const names = match[1]
|
|
95
|
+
.split(',')
|
|
96
|
+
.map((item) => item.trim())
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
|
|
99
|
+
if (!names.includes(importName)) {
|
|
100
|
+
names.push(importName);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const replacement = `import { ${names.join(', ')} } from '@nestjs/common';`;
|
|
104
|
+
return content.replace(pattern, replacement);
|
|
105
|
+
}
|
|
106
|
+
|
|
87
107
|
function upsertEnvLines(filePath, lines) {
|
|
88
108
|
let content = '';
|
|
89
109
|
if (fs.existsSync(filePath)) {
|
|
@@ -244,10 +264,13 @@ function patchHealthController(targetRoot) {
|
|
|
244
264
|
}
|
|
245
265
|
|
|
246
266
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
267
|
+
content = ensureNestCommonImport(content, 'Post');
|
|
268
|
+
|
|
247
269
|
if (!content.includes("from '@forgeon/db-prisma';")) {
|
|
270
|
+
const nestCommonImport = content.match(/import\s*\{[^}]*\}\s*from '@nestjs\/common';/m)?.[0];
|
|
248
271
|
const anchor = content.includes("import { I18nService } from 'nestjs-i18n';")
|
|
249
272
|
? "import { I18nService } from 'nestjs-i18n';"
|
|
250
|
-
:
|
|
273
|
+
: nestCommonImport;
|
|
251
274
|
content = ensureLineAfter(content, anchor, "import { PrismaService } from '@forgeon/db-prisma';");
|
|
252
275
|
}
|
|
253
276
|
|
|
@@ -256,8 +279,9 @@ function patchHealthController(targetRoot) {
|
|
|
256
279
|
if (constructorMatch) {
|
|
257
280
|
const original = constructorMatch[0];
|
|
258
281
|
const inner = constructorMatch[1].trimEnd();
|
|
259
|
-
const
|
|
260
|
-
const
|
|
282
|
+
const normalizedInner = inner.replace(/,\s*$/, '');
|
|
283
|
+
const separator = normalizedInner.length > 0 ? ',' : '';
|
|
284
|
+
const next = `constructor(${normalizedInner}${separator}
|
|
261
285
|
private readonly prisma: PrismaService,
|
|
262
286
|
) {`;
|
|
263
287
|
content = content.replace(original, next);
|
|
@@ -293,6 +317,49 @@ function patchHealthController(targetRoot) {
|
|
|
293
317
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
294
318
|
}
|
|
295
319
|
|
|
320
|
+
function patchWebApp(targetRoot) {
|
|
321
|
+
const filePath = path.join(targetRoot, 'apps', 'web', 'src', 'App.tsx');
|
|
322
|
+
if (!fs.existsSync(filePath)) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
327
|
+
|
|
328
|
+
if (!content.includes('dbProbeResult')) {
|
|
329
|
+
const stateAnchor = ' const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);';
|
|
330
|
+
if (content.includes(stateAnchor)) {
|
|
331
|
+
content = ensureLineAfter(
|
|
332
|
+
content,
|
|
333
|
+
stateAnchor,
|
|
334
|
+
' const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);',
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
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)
|
|
343
|
+
? " <button onClick={() => runProbe(setDbProbeResult, '/health/db', { method: 'POST' })}>\n Check database (create user)\n </button>"
|
|
344
|
+
: " <button onClick={() => runProbe(setDbProbeResult, '/api/health/db', { method: 'POST' })}>\n Check database (create user)\n </button>";
|
|
345
|
+
|
|
346
|
+
if (content.includes(buttonAnchor)) {
|
|
347
|
+
content = ensureLineAfter(content, buttonAnchor, dbButton);
|
|
348
|
+
} else if (content.includes(buttonAnchorI18n)) {
|
|
349
|
+
content = ensureLineAfter(content, buttonAnchorI18n, dbButton);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
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)}");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
361
|
+
}
|
|
362
|
+
|
|
296
363
|
function patchApiDockerfile(targetRoot) {
|
|
297
364
|
const dockerfilePath = path.join(targetRoot, 'apps', 'api', 'Dockerfile');
|
|
298
365
|
if (!fs.existsSync(dockerfilePath)) {
|
|
@@ -300,34 +367,26 @@ function patchApiDockerfile(targetRoot) {
|
|
|
300
367
|
}
|
|
301
368
|
|
|
302
369
|
let content = fs.readFileSync(dockerfilePath, 'utf8').replace(/\r\n/g, '\n');
|
|
303
|
-
content = ensureLineAfter(
|
|
304
|
-
|
|
305
|
-
'COPY apps/api/package.json apps/api/package.json',
|
|
306
|
-
'COPY apps/api/prisma apps/api/prisma',
|
|
307
|
-
);
|
|
308
|
-
content = ensureLineAfter(
|
|
309
|
-
content,
|
|
310
|
-
'COPY packages/core/package.json packages/core/package.json',
|
|
311
|
-
'COPY packages/db-prisma/package.json packages/db-prisma/package.json',
|
|
312
|
-
);
|
|
370
|
+
content = ensureLineAfter(content, 'COPY apps/api/package.json apps/api/package.json', 'COPY apps/api/prisma apps/api/prisma');
|
|
371
|
+
content = ensureLineAfter(content, 'COPY packages/core/package.json packages/core/package.json', 'COPY packages/db-prisma/package.json packages/db-prisma/package.json');
|
|
313
372
|
content = ensureLineAfter(content, 'COPY packages/core packages/core', 'COPY packages/db-prisma packages/db-prisma');
|
|
314
373
|
|
|
315
|
-
content = content
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
content = ensureLineBefore(
|
|
324
|
-
content,
|
|
325
|
-
'RUN pnpm --filter @forgeon/api build',
|
|
326
|
-
'RUN pnpm --filter @forgeon/api prisma:generate',
|
|
374
|
+
content = content
|
|
375
|
+
.replace(/^RUN pnpm --filter @forgeon\/db-prisma build\r?\n?/gm, '')
|
|
376
|
+
.replace(/^RUN pnpm --filter @forgeon\/core build\r?\n?/gm, '')
|
|
377
|
+
.replace(/^RUN pnpm --filter @forgeon\/api prisma:generate\r?\n?/gm, '')
|
|
378
|
+
.replace(/^CMD \["node", "apps\/api\/dist\/main\.js"\]\r?\n?/gm, '')
|
|
379
|
+
.replace(
|
|
380
|
+
/^CMD \["sh", "-c", "pnpm --filter @forgeon\/api prisma:migrate:deploy && node apps\/api\/dist\/main\.js"\]\r?\n?/gm,
|
|
381
|
+
'',
|
|
327
382
|
);
|
|
328
|
-
}
|
|
329
383
|
|
|
330
|
-
|
|
384
|
+
content = ensureLineBefore(content, 'RUN pnpm --filter @forgeon/api build', 'RUN pnpm --filter @forgeon/core build');
|
|
385
|
+
content = ensureLineBefore(content, 'RUN pnpm --filter @forgeon/api build', 'RUN pnpm --filter @forgeon/db-prisma build');
|
|
386
|
+
content = ensureLineBefore(content, 'RUN pnpm --filter @forgeon/api build', 'RUN pnpm --filter @forgeon/api prisma:generate');
|
|
387
|
+
content = `${content.trimEnd()}\nCMD ["sh", "-c", "pnpm --filter @forgeon/api prisma:migrate:deploy && node apps/api/dist/main.js"]\n`;
|
|
388
|
+
|
|
389
|
+
fs.writeFileSync(dockerfilePath, content, 'utf8');
|
|
331
390
|
}
|
|
332
391
|
|
|
333
392
|
function patchCompose(targetRoot) {
|
|
@@ -337,6 +396,29 @@ function patchCompose(targetRoot) {
|
|
|
337
396
|
}
|
|
338
397
|
|
|
339
398
|
let content = fs.readFileSync(composePath, 'utf8').replace(/\r\n/g, '\n');
|
|
399
|
+
|
|
400
|
+
const dbServiceBlock = ` db:
|
|
401
|
+
image: postgres:16-alpine
|
|
402
|
+
restart: unless-stopped
|
|
403
|
+
environment:
|
|
404
|
+
POSTGRES_USER: \${POSTGRES_USER}
|
|
405
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD}
|
|
406
|
+
POSTGRES_DB: \${POSTGRES_DB}
|
|
407
|
+
ports:
|
|
408
|
+
- "5432:5432"
|
|
409
|
+
volumes:
|
|
410
|
+
- db_data:/var/lib/postgresql/data
|
|
411
|
+
healthcheck:
|
|
412
|
+
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER}"]
|
|
413
|
+
interval: 10s
|
|
414
|
+
timeout: 5s
|
|
415
|
+
retries: 10
|
|
416
|
+
`;
|
|
417
|
+
|
|
418
|
+
if (!/\n\s{2}db:\n/.test(content)) {
|
|
419
|
+
content = content.replace(/^services:\n/m, `services:\n${dbServiceBlock}\n`);
|
|
420
|
+
}
|
|
421
|
+
|
|
340
422
|
if (!content.includes('DATABASE_URL: ${DATABASE_URL}')) {
|
|
341
423
|
content = content.replace(
|
|
342
424
|
/^(\s+API_PREFIX:.*)$/m,
|
|
@@ -345,6 +427,22 @@ function patchCompose(targetRoot) {
|
|
|
345
427
|
);
|
|
346
428
|
}
|
|
347
429
|
|
|
430
|
+
if (!content.includes('depends_on:') || !content.includes('condition: service_healthy')) {
|
|
431
|
+
content = content.replace(
|
|
432
|
+
/^(\s{2}api:\n[\s\S]*?^\s{4}environment:\n(?:\s{6}.+\n)+)/m,
|
|
433
|
+
`$1 depends_on:
|
|
434
|
+
db:
|
|
435
|
+
condition: service_healthy
|
|
436
|
+
`,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!/^volumes:\n/m.test(content)) {
|
|
441
|
+
content = `${content.trimEnd()}\n\nvolumes:\n db_data:\n`;
|
|
442
|
+
} else if (!/^\s{2}db_data:\s*$/m.test(content)) {
|
|
443
|
+
content = `${content.trimEnd()}\n db_data:\n`;
|
|
444
|
+
}
|
|
445
|
+
|
|
348
446
|
fs.writeFileSync(composePath, `${content.trimEnd()}\n`, 'utf8');
|
|
349
447
|
}
|
|
350
448
|
|
|
@@ -381,13 +479,14 @@ Configuration (env):
|
|
|
381
479
|
}
|
|
382
480
|
|
|
383
481
|
export function applyDbPrismaModule({ packageRoot, targetRoot }) {
|
|
384
|
-
|
|
385
|
-
|
|
482
|
+
copyFromPreset(packageRoot, targetRoot, path.join('packages', 'db-prisma'));
|
|
483
|
+
copyFromPreset(packageRoot, targetRoot, path.join('apps', 'api', 'prisma'));
|
|
386
484
|
|
|
387
485
|
patchApiPackage(targetRoot);
|
|
388
486
|
patchRootPackage(targetRoot);
|
|
389
487
|
patchAppModule(targetRoot);
|
|
390
488
|
patchHealthController(targetRoot);
|
|
489
|
+
patchWebApp(targetRoot);
|
|
391
490
|
patchApiDockerfile(targetRoot);
|
|
392
491
|
patchCompose(targetRoot);
|
|
393
492
|
patchReadme(targetRoot);
|
|
@@ -396,6 +495,9 @@ export function applyDbPrismaModule({ packageRoot, targetRoot }) {
|
|
|
396
495
|
'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app?schema=public',
|
|
397
496
|
]);
|
|
398
497
|
upsertEnvLines(path.join(targetRoot, 'infra', 'docker', '.env.example'), [
|
|
498
|
+
'POSTGRES_USER=postgres',
|
|
499
|
+
'POSTGRES_PASSWORD=postgres',
|
|
500
|
+
'POSTGRES_DB=app',
|
|
399
501
|
'DATABASE_URL=postgresql://postgres:postgres@db:5432/app?schema=public',
|
|
400
502
|
]);
|
|
401
503
|
}
|
|
@@ -261,6 +261,7 @@ describe('addModule', () => {
|
|
|
261
261
|
projectName: 'demo-i18n',
|
|
262
262
|
frontend: 'react',
|
|
263
263
|
db: 'prisma',
|
|
264
|
+
dbPrismaEnabled: true,
|
|
264
265
|
i18nEnabled: false,
|
|
265
266
|
proxy: 'caddy',
|
|
266
267
|
});
|
|
@@ -394,13 +395,17 @@ describe('addModule', () => {
|
|
|
394
395
|
assert.doesNotMatch(i18nTs, /I18N_DEFAULT_LANG/);
|
|
395
396
|
|
|
396
397
|
const rootPackage = fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8');
|
|
398
|
+
assert.match(rootPackage, /"forgeon:sync-integrations"/);
|
|
397
399
|
assert.match(rootPackage, /"i18n:sync"/);
|
|
398
400
|
assert.match(rootPackage, /"i18n:check"/);
|
|
399
401
|
assert.match(rootPackage, /"i18n:types"/);
|
|
400
402
|
assert.match(rootPackage, /"i18n:add"/);
|
|
403
|
+
assert.match(rootPackage, /"ts-morph":/);
|
|
401
404
|
|
|
402
405
|
const i18nAddScriptPath = path.join(projectRoot, 'scripts', 'i18n-add.mjs');
|
|
403
406
|
assert.equal(fs.existsSync(i18nAddScriptPath), true);
|
|
407
|
+
const syncScriptPath = path.join(projectRoot, 'scripts', 'forgeon-sync-integrations.mjs');
|
|
408
|
+
assert.equal(fs.existsSync(syncScriptPath), true);
|
|
404
409
|
|
|
405
410
|
const caddyDockerfile = fs.readFileSync(
|
|
406
411
|
path.join(projectRoot, 'infra', 'docker', 'caddy.Dockerfile'),
|
|
@@ -444,6 +449,7 @@ describe('addModule', () => {
|
|
|
444
449
|
projectName: 'demo-logger',
|
|
445
450
|
frontend: 'react',
|
|
446
451
|
db: 'prisma',
|
|
452
|
+
dbPrismaEnabled: true,
|
|
447
453
|
i18nEnabled: false,
|
|
448
454
|
proxy: 'caddy',
|
|
449
455
|
});
|
|
@@ -536,6 +542,7 @@ describe('addModule', () => {
|
|
|
536
542
|
projectName: 'demo-swagger',
|
|
537
543
|
frontend: 'react',
|
|
538
544
|
db: 'prisma',
|
|
545
|
+
dbPrismaEnabled: true,
|
|
539
546
|
i18nEnabled: false,
|
|
540
547
|
proxy: 'caddy',
|
|
541
548
|
});
|
|
@@ -629,6 +636,7 @@ describe('addModule', () => {
|
|
|
629
636
|
projectName: 'demo-swagger-i18n',
|
|
630
637
|
frontend: 'react',
|
|
631
638
|
db: 'prisma',
|
|
639
|
+
dbPrismaEnabled: true,
|
|
632
640
|
i18nEnabled: true,
|
|
633
641
|
proxy: 'caddy',
|
|
634
642
|
});
|
|
@@ -642,11 +650,19 @@ describe('addModule', () => {
|
|
|
642
650
|
assert.equal(result.applied, true);
|
|
643
651
|
|
|
644
652
|
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
645
|
-
|
|
646
|
-
assert.
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
);
|
|
653
|
+
const loadMatch = appModule.match(/load: \[([^\]]+)\]/);
|
|
654
|
+
assert.ok(loadMatch);
|
|
655
|
+
assert.match(loadMatch[1], /coreConfig/);
|
|
656
|
+
assert.match(loadMatch[1], /dbPrismaConfig/);
|
|
657
|
+
assert.match(loadMatch[1], /i18nConfig/);
|
|
658
|
+
assert.match(loadMatch[1], /swaggerConfig/);
|
|
659
|
+
|
|
660
|
+
const validateMatch = appModule.match(/validate: createEnvValidator\(\[([^\]]+)\]\)/);
|
|
661
|
+
assert.ok(validateMatch);
|
|
662
|
+
assert.match(validateMatch[1], /coreEnvSchema/);
|
|
663
|
+
assert.match(validateMatch[1], /dbPrismaEnvSchema/);
|
|
664
|
+
assert.match(validateMatch[1], /i18nEnvSchema/);
|
|
665
|
+
assert.match(validateMatch[1], /swaggerEnvSchema/);
|
|
650
666
|
assert.match(appModule, /ForgeonSwaggerModule/);
|
|
651
667
|
assert.match(appModule, /ForgeonI18nModule/);
|
|
652
668
|
} finally {
|
|
@@ -667,6 +683,7 @@ describe('addModule', () => {
|
|
|
667
683
|
projectName: 'demo-swagger-logger',
|
|
668
684
|
frontend: 'react',
|
|
669
685
|
db: 'prisma',
|
|
686
|
+
dbPrismaEnabled: true,
|
|
670
687
|
i18nEnabled: true,
|
|
671
688
|
proxy: 'caddy',
|
|
672
689
|
});
|
|
@@ -686,14 +703,21 @@ describe('addModule', () => {
|
|
|
686
703
|
assert.equal(loggerResult.applied, true);
|
|
687
704
|
|
|
688
705
|
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
);
|
|
693
|
-
assert.match(
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
706
|
+
const loadMatch = appModule.match(/load: \[([^\]]+)\]/);
|
|
707
|
+
assert.ok(loadMatch);
|
|
708
|
+
assert.match(loadMatch[1], /coreConfig/);
|
|
709
|
+
assert.match(loadMatch[1], /dbPrismaConfig/);
|
|
710
|
+
assert.match(loadMatch[1], /i18nConfig/);
|
|
711
|
+
assert.match(loadMatch[1], /swaggerConfig/);
|
|
712
|
+
assert.match(loadMatch[1], /loggerConfig/);
|
|
713
|
+
|
|
714
|
+
const validateMatch = appModule.match(/validate: createEnvValidator\(\[([^\]]+)\]\)/);
|
|
715
|
+
assert.ok(validateMatch);
|
|
716
|
+
assert.match(validateMatch[1], /coreEnvSchema/);
|
|
717
|
+
assert.match(validateMatch[1], /dbPrismaEnvSchema/);
|
|
718
|
+
assert.match(validateMatch[1], /i18nEnvSchema/);
|
|
719
|
+
assert.match(validateMatch[1], /swaggerEnvSchema/);
|
|
720
|
+
assert.match(validateMatch[1], /loggerEnvSchema/);
|
|
697
721
|
assert.match(appModule, /ForgeonSwaggerModule/);
|
|
698
722
|
assert.match(appModule, /ForgeonLoggerModule/);
|
|
699
723
|
} finally {
|
|
@@ -714,6 +738,7 @@ describe('addModule', () => {
|
|
|
714
738
|
projectName: 'demo-logger-i18n',
|
|
715
739
|
frontend: 'react',
|
|
716
740
|
db: 'prisma',
|
|
741
|
+
dbPrismaEnabled: true,
|
|
717
742
|
i18nEnabled: false,
|
|
718
743
|
proxy: 'caddy',
|
|
719
744
|
});
|
|
@@ -770,6 +795,7 @@ describe('addModule', () => {
|
|
|
770
795
|
projectName: 'demo-swagger-i18n-order',
|
|
771
796
|
frontend: 'react',
|
|
772
797
|
db: 'prisma',
|
|
798
|
+
dbPrismaEnabled: true,
|
|
773
799
|
i18nEnabled: false,
|
|
774
800
|
proxy: 'caddy',
|
|
775
801
|
});
|
|
@@ -826,6 +852,7 @@ describe('addModule', () => {
|
|
|
826
852
|
projectName: 'demo-mixed-order',
|
|
827
853
|
frontend: 'react',
|
|
828
854
|
db: 'prisma',
|
|
855
|
+
dbPrismaEnabled: true,
|
|
829
856
|
i18nEnabled: false,
|
|
830
857
|
proxy: 'caddy',
|
|
831
858
|
});
|
|
@@ -879,6 +906,7 @@ describe('addModule', () => {
|
|
|
879
906
|
projectName: 'demo-jwt-db',
|
|
880
907
|
frontend: 'react',
|
|
881
908
|
db: 'prisma',
|
|
909
|
+
dbPrismaEnabled: true,
|
|
882
910
|
i18nEnabled: true,
|
|
883
911
|
proxy: 'caddy',
|
|
884
912
|
});
|
|
@@ -944,6 +972,7 @@ describe('addModule', () => {
|
|
|
944
972
|
projectName: 'demo-jwt-nodb',
|
|
945
973
|
frontend: 'react',
|
|
946
974
|
db: 'prisma',
|
|
975
|
+
dbPrismaEnabled: true,
|
|
947
976
|
i18nEnabled: false,
|
|
948
977
|
proxy: 'caddy',
|
|
949
978
|
});
|
|
@@ -995,6 +1024,7 @@ describe('addModule', () => {
|
|
|
995
1024
|
projectName: `demo-db-${sequence.join('-')}`,
|
|
996
1025
|
frontend: 'react',
|
|
997
1026
|
db: 'prisma',
|
|
1027
|
+
dbPrismaEnabled: true,
|
|
998
1028
|
i18nEnabled: false,
|
|
999
1029
|
proxy: 'caddy',
|
|
1000
1030
|
});
|
|
@@ -1023,6 +1053,7 @@ describe('addModule', () => {
|
|
|
1023
1053
|
projectName: 'demo-db-last',
|
|
1024
1054
|
frontend: 'react',
|
|
1025
1055
|
db: 'prisma',
|
|
1056
|
+
dbPrismaEnabled: true,
|
|
1026
1057
|
i18nEnabled: false,
|
|
1027
1058
|
proxy: 'caddy',
|
|
1028
1059
|
});
|
package/src/modules/i18n.mjs
CHANGED
|
@@ -169,11 +169,6 @@ function patchApiDockerfile(targetRoot) {
|
|
|
169
169
|
content = ensureLineAfter(
|
|
170
170
|
content,
|
|
171
171
|
'COPY packages/core/package.json packages/core/package.json',
|
|
172
|
-
'COPY packages/db-prisma/package.json packages/db-prisma/package.json',
|
|
173
|
-
);
|
|
174
|
-
content = ensureLineAfter(
|
|
175
|
-
content,
|
|
176
|
-
'COPY packages/db-prisma/package.json packages/db-prisma/package.json',
|
|
177
172
|
'COPY packages/i18n-contracts/package.json packages/i18n-contracts/package.json',
|
|
178
173
|
);
|
|
179
174
|
content = ensureLineAfter(
|
|
@@ -181,16 +176,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
181
176
|
'COPY packages/i18n-contracts/package.json packages/i18n-contracts/package.json',
|
|
182
177
|
'COPY packages/i18n/package.json packages/i18n/package.json',
|
|
183
178
|
);
|
|
184
|
-
content = ensureLineAfter(
|
|
185
|
-
content,
|
|
186
|
-
'COPY packages/core packages/core',
|
|
187
|
-
'COPY packages/db-prisma packages/db-prisma',
|
|
188
|
-
);
|
|
189
|
-
content = ensureLineAfter(
|
|
190
|
-
content,
|
|
191
|
-
'COPY packages/db-prisma packages/db-prisma',
|
|
192
|
-
'COPY packages/i18n-contracts packages/i18n-contracts',
|
|
193
|
-
);
|
|
179
|
+
content = ensureLineAfter(content, 'COPY packages/core packages/core', 'COPY packages/i18n-contracts packages/i18n-contracts');
|
|
194
180
|
content = ensureLineAfter(
|
|
195
181
|
content,
|
|
196
182
|
'COPY packages/i18n-contracts packages/i18n-contracts',
|
|
@@ -199,28 +185,26 @@ function patchApiDockerfile(targetRoot) {
|
|
|
199
185
|
|
|
200
186
|
content = content
|
|
201
187
|
.replace(/^RUN pnpm --filter @forgeon\/core build\r?\n?/gm, '')
|
|
202
|
-
.replace(/^RUN pnpm --filter @forgeon\/db-prisma build\r?\n?/gm, '')
|
|
203
188
|
.replace(/^RUN pnpm --filter @forgeon\/i18n-contracts build\r?\n?/gm, '')
|
|
204
189
|
.replace(/^RUN pnpm --filter @forgeon\/i18n build\r?\n?/gm, '');
|
|
205
190
|
|
|
191
|
+
const apiBuildAnchor = content.includes('RUN pnpm --filter @forgeon/api prisma:generate')
|
|
192
|
+
? 'RUN pnpm --filter @forgeon/api prisma:generate'
|
|
193
|
+
: 'RUN pnpm --filter @forgeon/api build';
|
|
194
|
+
|
|
206
195
|
content = ensureLineBefore(
|
|
207
196
|
content,
|
|
208
|
-
|
|
197
|
+
apiBuildAnchor,
|
|
209
198
|
'RUN pnpm --filter @forgeon/core build',
|
|
210
199
|
);
|
|
211
200
|
content = ensureLineBefore(
|
|
212
201
|
content,
|
|
213
|
-
|
|
214
|
-
'RUN pnpm --filter @forgeon/db-prisma build',
|
|
215
|
-
);
|
|
216
|
-
content = ensureLineBefore(
|
|
217
|
-
content,
|
|
218
|
-
'RUN pnpm --filter @forgeon/api prisma:generate',
|
|
202
|
+
apiBuildAnchor,
|
|
219
203
|
'RUN pnpm --filter @forgeon/i18n-contracts build',
|
|
220
204
|
);
|
|
221
205
|
content = ensureLineBefore(
|
|
222
206
|
content,
|
|
223
|
-
|
|
207
|
+
apiBuildAnchor,
|
|
224
208
|
'RUN pnpm --filter @forgeon/i18n build',
|
|
225
209
|
);
|
|
226
210
|
|
|
@@ -291,8 +275,10 @@ function patchCompose(targetRoot) {
|
|
|
291
275
|
|
|
292
276
|
let content = fs.readFileSync(composePath, 'utf8').replace(/\r\n/g, '\n');
|
|
293
277
|
if (!content.includes('I18N_DEFAULT_LANG: ${I18N_DEFAULT_LANG}')) {
|
|
278
|
+
const hasDatabaseUrl = /^(\s+DATABASE_URL:.*)$/m.test(content);
|
|
279
|
+
const anchorPattern = hasDatabaseUrl ? /^(\s+DATABASE_URL:.*)$/m : /^(\s+API_PREFIX:.*)$/m;
|
|
294
280
|
content = content.replace(
|
|
295
|
-
|
|
281
|
+
anchorPattern,
|
|
296
282
|
`$1
|
|
297
283
|
I18N_DEFAULT_LANG: \${I18N_DEFAULT_LANG}
|
|
298
284
|
I18N_FALLBACK_LANG: \${I18N_FALLBACK_LANG}`,
|
|
@@ -311,13 +297,11 @@ function patchApiPackage(targetRoot) {
|
|
|
311
297
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
312
298
|
ensureBuildSteps(packageJson, 'predev', [
|
|
313
299
|
'pnpm --filter @forgeon/core build',
|
|
314
|
-
'pnpm --filter @forgeon/db-prisma build',
|
|
315
300
|
'pnpm --filter @forgeon/i18n-contracts build',
|
|
316
301
|
'pnpm --filter @forgeon/i18n build',
|
|
317
302
|
]);
|
|
318
303
|
ensureDependency(packageJson, '@forgeon/i18n', 'workspace:*');
|
|
319
304
|
ensureDependency(packageJson, '@forgeon/i18n-contracts', 'workspace:*');
|
|
320
|
-
ensureDependency(packageJson, '@forgeon/db-prisma', 'workspace:*');
|
|
321
305
|
ensureDependency(packageJson, 'nestjs-i18n', '^10.5.1');
|
|
322
306
|
writeJson(packagePath, packageJson);
|
|
323
307
|
}
|
|
@@ -544,7 +528,6 @@ function patchRootPackage(targetRoot) {
|
|
|
544
528
|
|
|
545
529
|
export function applyI18nModule({ packageRoot, targetRoot }) {
|
|
546
530
|
copyFromBase(packageRoot, targetRoot, path.join('scripts', 'i18n-add.mjs'));
|
|
547
|
-
copyFromBase(packageRoot, targetRoot, path.join('packages', 'db-prisma'));
|
|
548
531
|
copyFromBase(packageRoot, targetRoot, path.join('packages', 'i18n'));
|
|
549
532
|
copyFromBase(packageRoot, targetRoot, path.join('resources', 'i18n'));
|
|
550
533
|
|
package/src/modules/logger.mjs
CHANGED
|
@@ -238,9 +238,12 @@ function patchApiDockerfile(targetRoot) {
|
|
|
238
238
|
content = ensureLineAfter(content, sourceAnchor, 'COPY packages/logger packages/logger');
|
|
239
239
|
|
|
240
240
|
content = content.replace(/^RUN pnpm --filter @forgeon\/logger build\r?\n?/gm, '');
|
|
241
|
+
const buildAnchor = content.includes('RUN pnpm --filter @forgeon/api prisma:generate')
|
|
242
|
+
? 'RUN pnpm --filter @forgeon/api prisma:generate'
|
|
243
|
+
: 'RUN pnpm --filter @forgeon/api build';
|
|
241
244
|
content = ensureLineBefore(
|
|
242
245
|
content,
|
|
243
|
-
|
|
246
|
+
buildAnchor,
|
|
244
247
|
'RUN pnpm --filter @forgeon/logger build',
|
|
245
248
|
);
|
|
246
249
|
|
package/src/modules/swagger.mjs
CHANGED
|
@@ -241,9 +241,12 @@ function patchApiDockerfile(targetRoot) {
|
|
|
241
241
|
content = ensureLineAfter(content, sourceAnchor, 'COPY packages/swagger packages/swagger');
|
|
242
242
|
|
|
243
243
|
content = content.replace(/^RUN pnpm --filter @forgeon\/swagger build\r?\n?/gm, '');
|
|
244
|
+
const buildAnchor = content.includes('RUN pnpm --filter @forgeon/api prisma:generate')
|
|
245
|
+
? 'RUN pnpm --filter @forgeon/api prisma:generate'
|
|
246
|
+
: 'RUN pnpm --filter @forgeon/api build';
|
|
244
247
|
content = ensureLineBefore(
|
|
245
248
|
content,
|
|
246
|
-
|
|
249
|
+
buildAnchor,
|
|
247
250
|
'RUN pnpm --filter @forgeon/swagger build',
|
|
248
251
|
);
|
|
249
252
|
|
|
@@ -258,8 +261,10 @@ function patchCompose(targetRoot) {
|
|
|
258
261
|
|
|
259
262
|
let content = fs.readFileSync(composePath, 'utf8').replace(/\r\n/g, '\n');
|
|
260
263
|
if (!content.includes('SWAGGER_ENABLED: ${SWAGGER_ENABLED}')) {
|
|
264
|
+
const hasDatabaseUrl = /^(\s+DATABASE_URL:.*)$/m.test(content);
|
|
265
|
+
const anchorPattern = hasDatabaseUrl ? /^(\s+DATABASE_URL:.*)$/m : /^(\s+API_PREFIX:.*)$/m;
|
|
261
266
|
content = content.replace(
|
|
262
|
-
|
|
267
|
+
anchorPattern,
|
|
263
268
|
`$1
|
|
264
269
|
SWAGGER_ENABLED: \${SWAGGER_ENABLED}
|
|
265
270
|
SWAGGER_PATH: \${SWAGGER_PATH}
|