create-forgeon 0.1.33 → 0.1.35
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 +1 -1
- package/src/modules/db-prisma.mjs +401 -0
- package/src/modules/executor.mjs +2 -0
- package/src/modules/executor.test.mjs +414 -6
- package/src/modules/i18n.mjs +244 -22
- package/src/modules/logger.mjs +76 -27
- package/src/modules/registry.mjs +8 -0
- package/src/modules/swagger.mjs +14 -4
- package/templates/module-fragments/db-prisma/00_title.md +6 -0
- package/templates/module-fragments/db-prisma/10_overview.md +10 -0
- package/templates/module-fragments/db-prisma/20_scope.md +14 -0
- package/templates/module-fragments/db-prisma/90_status_implemented.md +4 -0
- package/templates/module-presets/swagger/packages/swagger/src/setup-swagger.ts +1 -1
package/src/modules/i18n.mjs
CHANGED
|
@@ -34,6 +34,31 @@ function ensureScript(packageJson, name, command) {
|
|
|
34
34
|
packageJson.scripts[name] = command;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function ensureBuildSteps(packageJson, scriptName, requiredCommands) {
|
|
38
|
+
if (!packageJson.scripts) {
|
|
39
|
+
packageJson.scripts = {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const current = packageJson.scripts[scriptName];
|
|
43
|
+
const steps =
|
|
44
|
+
typeof current === 'string' && current.trim().length > 0
|
|
45
|
+
? current
|
|
46
|
+
.split('&&')
|
|
47
|
+
.map((item) => item.trim())
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
: [];
|
|
50
|
+
|
|
51
|
+
for (const command of requiredCommands) {
|
|
52
|
+
if (!steps.includes(command)) {
|
|
53
|
+
steps.push(command);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (steps.length > 0) {
|
|
58
|
+
packageJson.scripts[scriptName] = steps.join(' && ');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
37
62
|
function upsertEnvLines(filePath, lines) {
|
|
38
63
|
let content = '';
|
|
39
64
|
if (fs.existsSync(filePath)) {
|
|
@@ -87,6 +112,48 @@ function ensureLineBefore(content, anchorLine, lineToInsert) {
|
|
|
87
112
|
return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
|
|
88
113
|
}
|
|
89
114
|
|
|
115
|
+
function ensureLoadItem(content, itemName) {
|
|
116
|
+
const pattern = /load:\s*\[([^\]]*)\]/m;
|
|
117
|
+
const match = content.match(pattern);
|
|
118
|
+
if (!match) {
|
|
119
|
+
return content;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const rawList = match[1];
|
|
123
|
+
const items = rawList
|
|
124
|
+
.split(',')
|
|
125
|
+
.map((item) => item.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
|
|
128
|
+
if (!items.includes(itemName)) {
|
|
129
|
+
items.push(itemName);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const next = `load: [${items.join(', ')}]`;
|
|
133
|
+
return content.replace(pattern, next);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function ensureValidatorSchema(content, schemaName) {
|
|
137
|
+
const pattern = /validate:\s*createEnvValidator\(\[([^\]]*)\]\)/m;
|
|
138
|
+
const match = content.match(pattern);
|
|
139
|
+
if (!match) {
|
|
140
|
+
return content;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rawList = match[1];
|
|
144
|
+
const items = rawList
|
|
145
|
+
.split(',')
|
|
146
|
+
.map((item) => item.trim())
|
|
147
|
+
.filter(Boolean);
|
|
148
|
+
|
|
149
|
+
if (!items.includes(schemaName)) {
|
|
150
|
+
items.push(schemaName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const next = `validate: createEnvValidator([${items.join(', ')}])`;
|
|
154
|
+
return content.replace(pattern, next);
|
|
155
|
+
}
|
|
156
|
+
|
|
90
157
|
function patchApiDockerfile(targetRoot) {
|
|
91
158
|
const dockerfilePath = path.join(targetRoot, 'apps', 'api', 'Dockerfile');
|
|
92
159
|
if (!fs.existsSync(dockerfilePath)) {
|
|
@@ -242,11 +309,12 @@ function patchApiPackage(targetRoot) {
|
|
|
242
309
|
}
|
|
243
310
|
|
|
244
311
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
'
|
|
248
|
-
'pnpm --filter @forgeon/
|
|
249
|
-
|
|
312
|
+
ensureBuildSteps(packageJson, 'predev', [
|
|
313
|
+
'pnpm --filter @forgeon/core build',
|
|
314
|
+
'pnpm --filter @forgeon/db-prisma build',
|
|
315
|
+
'pnpm --filter @forgeon/i18n-contracts build',
|
|
316
|
+
'pnpm --filter @forgeon/i18n build',
|
|
317
|
+
]);
|
|
250
318
|
ensureDependency(packageJson, '@forgeon/i18n', 'workspace:*');
|
|
251
319
|
ensureDependency(packageJson, '@forgeon/i18n-contracts', 'workspace:*');
|
|
252
320
|
ensureDependency(packageJson, '@forgeon/db-prisma', 'workspace:*');
|
|
@@ -261,16 +329,14 @@ function patchWebPackage(targetRoot) {
|
|
|
261
329
|
}
|
|
262
330
|
|
|
263
331
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
'
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
'pnpm --filter @forgeon/i18n-contracts build && pnpm --filter @forgeon/i18n-web build',
|
|
273
|
-
);
|
|
332
|
+
ensureBuildSteps(packageJson, 'predev', [
|
|
333
|
+
'pnpm --filter @forgeon/i18n-contracts build',
|
|
334
|
+
'pnpm --filter @forgeon/i18n-web build',
|
|
335
|
+
]);
|
|
336
|
+
ensureBuildSteps(packageJson, 'prebuild', [
|
|
337
|
+
'pnpm --filter @forgeon/i18n-contracts build',
|
|
338
|
+
'pnpm --filter @forgeon/i18n-web build',
|
|
339
|
+
]);
|
|
274
340
|
ensureDependency(packageJson, '@forgeon/i18n-contracts', 'workspace:*');
|
|
275
341
|
ensureDependency(packageJson, '@forgeon/i18n-web', 'workspace:*');
|
|
276
342
|
ensureDependency(packageJson, 'i18next', '^23.16.8');
|
|
@@ -278,6 +344,167 @@ function patchWebPackage(targetRoot) {
|
|
|
278
344
|
writeJson(packagePath, packageJson);
|
|
279
345
|
}
|
|
280
346
|
|
|
347
|
+
function patchAppModule(targetRoot) {
|
|
348
|
+
const appModulePath = path.join(targetRoot, 'apps', 'api', 'src', 'app.module.ts');
|
|
349
|
+
if (!fs.existsSync(appModulePath)) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let content = fs.readFileSync(appModulePath, 'utf8').replace(/\r\n/g, '\n');
|
|
354
|
+
if (!content.includes("from '@forgeon/i18n';")) {
|
|
355
|
+
if (content.includes("import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';")) {
|
|
356
|
+
content = ensureLineAfter(
|
|
357
|
+
content,
|
|
358
|
+
"import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';",
|
|
359
|
+
"import { ForgeonI18nModule, i18nConfig, i18nEnvSchema } from '@forgeon/i18n';",
|
|
360
|
+
);
|
|
361
|
+
} else if (
|
|
362
|
+
content.includes("import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';")
|
|
363
|
+
) {
|
|
364
|
+
content = ensureLineAfter(
|
|
365
|
+
content,
|
|
366
|
+
"import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';",
|
|
367
|
+
"import { ForgeonI18nModule, i18nConfig, i18nEnvSchema } from '@forgeon/i18n';",
|
|
368
|
+
);
|
|
369
|
+
} else if (
|
|
370
|
+
content.includes("import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';")
|
|
371
|
+
) {
|
|
372
|
+
content = ensureLineAfter(
|
|
373
|
+
content,
|
|
374
|
+
"import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';",
|
|
375
|
+
"import { ForgeonI18nModule, i18nConfig, i18nEnvSchema } from '@forgeon/i18n';",
|
|
376
|
+
);
|
|
377
|
+
} else {
|
|
378
|
+
content = ensureLineAfter(
|
|
379
|
+
content,
|
|
380
|
+
"import { ConfigModule } from '@nestjs/config';",
|
|
381
|
+
"import { ForgeonI18nModule, i18nConfig, i18nEnvSchema } from '@forgeon/i18n';",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!content.includes("import { join } from 'path';")) {
|
|
387
|
+
content = ensureLineBefore(
|
|
388
|
+
content,
|
|
389
|
+
"import { HealthController } from './health/health.controller';",
|
|
390
|
+
"import { join } from 'path';",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!content.includes('const i18nPath =')) {
|
|
395
|
+
content = ensureLineBefore(
|
|
396
|
+
content,
|
|
397
|
+
'@Module({',
|
|
398
|
+
"const i18nPath = join(__dirname, '..', '..', '..', 'resources', 'i18n');",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
content = ensureLoadItem(content, 'i18nConfig');
|
|
403
|
+
content = ensureValidatorSchema(content, 'i18nEnvSchema');
|
|
404
|
+
|
|
405
|
+
const i18nModuleBlock = ` ForgeonI18nModule.register({
|
|
406
|
+
path: i18nPath,
|
|
407
|
+
}),`;
|
|
408
|
+
if (!content.includes('ForgeonI18nModule.register({')) {
|
|
409
|
+
if (content.includes(' DbPrismaModule,')) {
|
|
410
|
+
content = ensureLineAfter(content, ' DbPrismaModule,', i18nModuleBlock);
|
|
411
|
+
} else if (content.includes(' ForgeonLoggerModule,')) {
|
|
412
|
+
content = ensureLineAfter(content, ' ForgeonLoggerModule,', i18nModuleBlock);
|
|
413
|
+
} else if (content.includes(' ForgeonSwaggerModule,')) {
|
|
414
|
+
content = ensureLineAfter(content, ' ForgeonSwaggerModule,', i18nModuleBlock);
|
|
415
|
+
} else {
|
|
416
|
+
content = ensureLineAfter(content, ' CoreErrorsModule,', i18nModuleBlock);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
fs.writeFileSync(appModulePath, `${content.trimEnd()}\n`, 'utf8');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function patchHealthController(targetRoot) {
|
|
424
|
+
const filePath = path.join(targetRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts');
|
|
425
|
+
if (!fs.existsSync(filePath)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
430
|
+
if (!content.includes("from 'nestjs-i18n';")) {
|
|
431
|
+
if (content.includes("import { PrismaService } from '@forgeon/db-prisma';")) {
|
|
432
|
+
content = ensureLineAfter(
|
|
433
|
+
content,
|
|
434
|
+
"import { PrismaService } from '@forgeon/db-prisma';",
|
|
435
|
+
"import { I18nService } from 'nestjs-i18n';",
|
|
436
|
+
);
|
|
437
|
+
} else {
|
|
438
|
+
content = ensureLineAfter(
|
|
439
|
+
content,
|
|
440
|
+
"import { BadRequestException, ConflictException, Controller, Get, Post, Query } from '@nestjs/common';",
|
|
441
|
+
"import { I18nService } from 'nestjs-i18n';",
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (!content.includes('private readonly i18n: I18nService')) {
|
|
447
|
+
const constructorMatch = content.match(/constructor\(([\s\S]*?)\)\s*\{/m);
|
|
448
|
+
if (constructorMatch) {
|
|
449
|
+
const original = constructorMatch[0];
|
|
450
|
+
const inner = constructorMatch[1].trimEnd();
|
|
451
|
+
const separator = inner.length > 0 ? ',' : '';
|
|
452
|
+
const next = `constructor(${inner}${separator}
|
|
453
|
+
private readonly i18n: I18nService,
|
|
454
|
+
) {`;
|
|
455
|
+
content = content.replace(original, next);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!content.includes('private translate(')) {
|
|
460
|
+
const translateMethod = `
|
|
461
|
+
private translate(key: string, lang?: string): string {
|
|
462
|
+
const value = this.i18n.t(key, { lang, defaultValue: key });
|
|
463
|
+
return typeof value === 'string' ? value : key;
|
|
464
|
+
}
|
|
465
|
+
`;
|
|
466
|
+
content = `${content.trimEnd()}\n${translateMethod}\n`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
content = content.replace(
|
|
470
|
+
/getHealth\(@Query\('lang'\)\s*_lang\?:\s*string\)/g,
|
|
471
|
+
"getHealth(@Query('lang') lang?: string)",
|
|
472
|
+
);
|
|
473
|
+
content = content.replace(
|
|
474
|
+
/getErrorProbe\(\)/g,
|
|
475
|
+
"getErrorProbe(@Query('lang') lang?: string)",
|
|
476
|
+
);
|
|
477
|
+
content = content.replace(
|
|
478
|
+
/getValidationProbe\(@Query\('value'\)\s*value\?:\s*string\)/g,
|
|
479
|
+
"getValidationProbe(@Query('value') value?: string, @Query('lang') lang?: string)",
|
|
480
|
+
);
|
|
481
|
+
content = content.replace(/message:\s*'OK',/g, "message: this.translate('common.actions.ok', lang),");
|
|
482
|
+
content = content.replace(/i18n:\s*'English',/g, "i18n: 'en',");
|
|
483
|
+
|
|
484
|
+
content = content.replace(
|
|
485
|
+
/message:\s*'Email already exists',/g,
|
|
486
|
+
"message: this.translate('errors.http.CONFLICT', lang),",
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
if (
|
|
490
|
+
content.includes("const translatedMessage = this.translate('validation.generic.required', lang);") === false &&
|
|
491
|
+
content.includes("if (!value || value.trim().length === 0) {")
|
|
492
|
+
) {
|
|
493
|
+
content = content.replace(
|
|
494
|
+
/if \(!value \|\| value\.trim\(\)\.length === 0\) \{\s*throw new BadRequestException\(\{\s*message:\s*'Field is required',\s*details:\s*\[\{ field: 'value', message: 'Field is required' \}\],\s*\}\);\s*\}/m,
|
|
495
|
+
`if (!value || value.trim().length === 0) {
|
|
496
|
+
const translatedMessage = this.translate('validation.generic.required', lang);
|
|
497
|
+
throw new BadRequestException({
|
|
498
|
+
message: translatedMessage,
|
|
499
|
+
details: [{ field: 'value', message: translatedMessage }],
|
|
500
|
+
});
|
|
501
|
+
}`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
506
|
+
}
|
|
507
|
+
|
|
281
508
|
function patchI18nPackage(targetRoot) {
|
|
282
509
|
const packagePath = path.join(targetRoot, 'packages', 'i18n', 'package.json');
|
|
283
510
|
if (!fs.existsSync(packagePath)) {
|
|
@@ -327,17 +554,12 @@ export function applyI18nModule({ packageRoot, targetRoot }) {
|
|
|
327
554
|
copyFromPreset(packageRoot, targetRoot, path.join('apps', 'web', 'src', 'i18n.ts'));
|
|
328
555
|
copyFromPreset(packageRoot, targetRoot, path.join('apps', 'web', 'src', 'main.tsx'));
|
|
329
556
|
|
|
330
|
-
copyFromBase(packageRoot, targetRoot, path.join('apps', 'api', 'src', 'app.module.ts'));
|
|
331
|
-
copyFromBase(
|
|
332
|
-
packageRoot,
|
|
333
|
-
targetRoot,
|
|
334
|
-
path.join('apps', 'api', 'src', 'health', 'health.controller.ts'),
|
|
335
|
-
);
|
|
336
|
-
|
|
337
557
|
patchI18nPackage(targetRoot);
|
|
338
558
|
patchApiPackage(targetRoot);
|
|
339
559
|
patchWebPackage(targetRoot);
|
|
340
560
|
patchRootPackage(targetRoot);
|
|
561
|
+
patchAppModule(targetRoot);
|
|
562
|
+
patchHealthController(targetRoot);
|
|
341
563
|
patchApiDockerfile(targetRoot);
|
|
342
564
|
patchProxyDockerfiles(targetRoot);
|
|
343
565
|
|
package/src/modules/logger.mjs
CHANGED
|
@@ -71,6 +71,48 @@ function upsertEnvLines(filePath, lines) {
|
|
|
71
71
|
fs.writeFileSync(filePath, next.replace(/^\n/, ''), 'utf8');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function ensureLoadItem(content, itemName) {
|
|
75
|
+
const pattern = /load:\s*\[([^\]]*)\]/m;
|
|
76
|
+
const match = content.match(pattern);
|
|
77
|
+
if (!match) {
|
|
78
|
+
return content;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const rawList = match[1];
|
|
82
|
+
const items = rawList
|
|
83
|
+
.split(',')
|
|
84
|
+
.map((item) => item.trim())
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
|
|
87
|
+
if (!items.includes(itemName)) {
|
|
88
|
+
items.push(itemName);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const next = `load: [${items.join(', ')}]`;
|
|
92
|
+
return content.replace(pattern, next);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensureValidatorSchema(content, schemaName) {
|
|
96
|
+
const pattern = /validate:\s*createEnvValidator\(\[([^\]]*)\]\)/m;
|
|
97
|
+
const match = content.match(pattern);
|
|
98
|
+
if (!match) {
|
|
99
|
+
return content;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const rawList = match[1];
|
|
103
|
+
const items = rawList
|
|
104
|
+
.split(',')
|
|
105
|
+
.map((item) => item.trim())
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
|
|
108
|
+
if (!items.includes(schemaName)) {
|
|
109
|
+
items.push(schemaName);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const next = `validate: createEnvValidator([${items.join(', ')}])`;
|
|
113
|
+
return content.replace(pattern, next);
|
|
114
|
+
}
|
|
115
|
+
|
|
74
116
|
function patchApiPackage(targetRoot) {
|
|
75
117
|
const packagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
|
|
76
118
|
if (!fs.existsSync(packagePath)) {
|
|
@@ -141,28 +183,32 @@ function patchAppModule(targetRoot) {
|
|
|
141
183
|
|
|
142
184
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
143
185
|
|
|
144
|
-
content
|
|
145
|
-
content,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
186
|
+
if (!content.includes("from '@forgeon/logger';")) {
|
|
187
|
+
if (content.includes("import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';")) {
|
|
188
|
+
content = ensureLineAfter(
|
|
189
|
+
content,
|
|
190
|
+
"import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';",
|
|
191
|
+
"import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';",
|
|
192
|
+
);
|
|
193
|
+
} else if (
|
|
194
|
+
content.includes("import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';")
|
|
195
|
+
) {
|
|
196
|
+
content = ensureLineAfter(
|
|
197
|
+
content,
|
|
198
|
+
"import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';",
|
|
199
|
+
"import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';",
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
content = ensureLineAfter(
|
|
203
|
+
content,
|
|
204
|
+
"import { ConfigModule } from '@nestjs/config';",
|
|
205
|
+
"import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';",
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
149
209
|
|
|
150
|
-
content = content
|
|
151
|
-
|
|
152
|
-
'load: [coreConfig, dbPrismaConfig, i18nConfig, loggerConfig],',
|
|
153
|
-
);
|
|
154
|
-
content = content.replace(
|
|
155
|
-
'load: [coreConfig, dbPrismaConfig],',
|
|
156
|
-
'load: [coreConfig, dbPrismaConfig, loggerConfig],',
|
|
157
|
-
);
|
|
158
|
-
content = content.replace(
|
|
159
|
-
'validate: createEnvValidator([coreEnvSchema, dbPrismaEnvSchema, i18nEnvSchema]),',
|
|
160
|
-
'validate: createEnvValidator([coreEnvSchema, dbPrismaEnvSchema, i18nEnvSchema, loggerEnvSchema]),',
|
|
161
|
-
);
|
|
162
|
-
content = content.replace(
|
|
163
|
-
'validate: createEnvValidator([coreEnvSchema, dbPrismaEnvSchema]),',
|
|
164
|
-
'validate: createEnvValidator([coreEnvSchema, dbPrismaEnvSchema, loggerEnvSchema]),',
|
|
165
|
-
);
|
|
210
|
+
content = ensureLoadItem(content, 'loggerConfig');
|
|
211
|
+
content = ensureValidatorSchema(content, 'loggerEnvSchema');
|
|
166
212
|
|
|
167
213
|
content = ensureLineAfter(content, ' CoreErrorsModule,', ' ForgeonLoggerModule,');
|
|
168
214
|
|
|
@@ -177,16 +223,19 @@ function patchApiDockerfile(targetRoot) {
|
|
|
177
223
|
|
|
178
224
|
let content = fs.readFileSync(dockerfilePath, 'utf8').replace(/\r\n/g, '\n');
|
|
179
225
|
|
|
226
|
+
const packageAnchor = content.includes('COPY packages/db-prisma/package.json packages/db-prisma/package.json')
|
|
227
|
+
? 'COPY packages/db-prisma/package.json packages/db-prisma/package.json'
|
|
228
|
+
: 'COPY packages/core/package.json packages/core/package.json';
|
|
180
229
|
content = ensureLineAfter(
|
|
181
230
|
content,
|
|
182
|
-
|
|
231
|
+
packageAnchor,
|
|
183
232
|
'COPY packages/logger/package.json packages/logger/package.json',
|
|
184
233
|
);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
'COPY packages/db-prisma packages/db-prisma'
|
|
188
|
-
'COPY packages/
|
|
189
|
-
);
|
|
234
|
+
|
|
235
|
+
const sourceAnchor = content.includes('COPY packages/db-prisma packages/db-prisma')
|
|
236
|
+
? 'COPY packages/db-prisma packages/db-prisma'
|
|
237
|
+
: 'COPY packages/core packages/core';
|
|
238
|
+
content = ensureLineAfter(content, sourceAnchor, 'COPY packages/logger packages/logger');
|
|
190
239
|
|
|
191
240
|
content = content.replace(/^RUN pnpm --filter @forgeon\/logger build\r?\n?/gm, '');
|
|
192
241
|
content = ensureLineBefore(
|
package/src/modules/registry.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
const MODULE_PRESETS = {
|
|
2
|
+
'db-prisma': {
|
|
3
|
+
id: 'db-prisma',
|
|
4
|
+
label: 'DB Prisma',
|
|
5
|
+
category: 'database-layer',
|
|
6
|
+
implemented: true,
|
|
7
|
+
description: 'Prisma/Postgres module wiring with env config, scripts, and DB probe endpoint.',
|
|
8
|
+
docFragments: ['00_title', '10_overview', '20_scope', '90_status_implemented'],
|
|
9
|
+
},
|
|
2
10
|
i18n: {
|
|
3
11
|
id: 'i18n',
|
|
4
12
|
label: 'I18n',
|
package/src/modules/swagger.mjs
CHANGED
|
@@ -197,6 +197,12 @@ function patchAppModule(targetRoot) {
|
|
|
197
197
|
"import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';",
|
|
198
198
|
"import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';",
|
|
199
199
|
);
|
|
200
|
+
} else {
|
|
201
|
+
content = ensureLineAfter(
|
|
202
|
+
content,
|
|
203
|
+
"import { ConfigModule } from '@nestjs/config';",
|
|
204
|
+
"import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';",
|
|
205
|
+
);
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
208
|
|
|
@@ -218,7 +224,9 @@ function patchApiDockerfile(targetRoot) {
|
|
|
218
224
|
|
|
219
225
|
const packageAnchor = content.includes('COPY packages/logger/package.json packages/logger/package.json')
|
|
220
226
|
? 'COPY packages/logger/package.json packages/logger/package.json'
|
|
221
|
-
: 'COPY packages/db-prisma/package.json packages/db-prisma/package.json'
|
|
227
|
+
: content.includes('COPY packages/db-prisma/package.json packages/db-prisma/package.json')
|
|
228
|
+
? 'COPY packages/db-prisma/package.json packages/db-prisma/package.json'
|
|
229
|
+
: 'COPY packages/core/package.json packages/core/package.json';
|
|
222
230
|
content = ensureLineAfter(
|
|
223
231
|
content,
|
|
224
232
|
packageAnchor,
|
|
@@ -227,7 +235,9 @@ function patchApiDockerfile(targetRoot) {
|
|
|
227
235
|
|
|
228
236
|
const sourceAnchor = content.includes('COPY packages/logger packages/logger')
|
|
229
237
|
? 'COPY packages/logger packages/logger'
|
|
230
|
-
: 'COPY packages/db-prisma packages/db-prisma'
|
|
238
|
+
: content.includes('COPY packages/db-prisma packages/db-prisma')
|
|
239
|
+
? 'COPY packages/db-prisma packages/db-prisma'
|
|
240
|
+
: 'COPY packages/core packages/core';
|
|
231
241
|
content = ensureLineAfter(content, sourceAnchor, 'COPY packages/swagger packages/swagger');
|
|
232
242
|
|
|
233
243
|
content = content.replace(/^RUN pnpm --filter @forgeon\/swagger build\r?\n?/gm, '');
|
|
@@ -284,8 +294,8 @@ Configuration (env):
|
|
|
284
294
|
- \`SWAGGER_VERSION=1.0.0\`
|
|
285
295
|
|
|
286
296
|
When enabled:
|
|
287
|
-
- UI endpoint: \`http://localhost:3000/docs\` (
|
|
288
|
-
- in Docker with proxy: \`http://localhost:8080/docs\`
|
|
297
|
+
- UI endpoint: \`http://localhost:3000/api/docs\` (respects global API prefix)
|
|
298
|
+
- in Docker with proxy: \`http://localhost:8080/api/docs\`
|
|
289
299
|
`;
|
|
290
300
|
|
|
291
301
|
if (content.includes('## Prisma In Docker Start')) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
Adds Prisma/Postgres database wiring to the API.
|
|
4
|
+
|
|
5
|
+
Included parts:
|
|
6
|
+
- `@forgeon/db-prisma` package
|
|
7
|
+
- Prisma schema + migration files in `apps/api/prisma`
|
|
8
|
+
- API scripts for `prisma generate/migrate/studio/seed`
|
|
9
|
+
- DB health probe endpoint wiring
|
|
10
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Applied Scope
|
|
2
|
+
|
|
3
|
+
- Adds `packages/db-prisma` workspace package
|
|
4
|
+
- Restores/creates `apps/api/prisma` schema and migrations
|
|
5
|
+
- Wires db config/env schema into API `ConfigModule` load/validation
|
|
6
|
+
- Registers `DbPrismaModule` in API `AppModule`
|
|
7
|
+
- Ensures `PrismaService` is available in health controller (`POST /api/health/db`)
|
|
8
|
+
- Updates API scripts and dependencies for Prisma workflows
|
|
9
|
+
- Updates API Docker build steps to include db package and prisma generate
|
|
10
|
+
- Ensures `DATABASE_URL` in:
|
|
11
|
+
- `apps/api/.env.example`
|
|
12
|
+
- `infra/docker/.env.example`
|
|
13
|
+
- `infra/docker/compose.yml`
|
|
14
|
+
|