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.
@@ -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
- ensureScript(
246
- packageJson,
247
- 'predev',
248
- 'pnpm --filter @forgeon/core build && pnpm --filter @forgeon/db-prisma build && pnpm --filter @forgeon/i18n-contracts build && pnpm --filter @forgeon/i18n build',
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
- ensureScript(
265
- packageJson,
266
- 'predev',
267
- 'pnpm --filter @forgeon/i18n-contracts build && pnpm --filter @forgeon/i18n-web build',
268
- );
269
- ensureScript(
270
- packageJson,
271
- 'prebuild',
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
 
@@ -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 = ensureLineAfter(
145
- content,
146
- "import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';",
147
- "import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';",
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.replace(
151
- 'load: [coreConfig, dbPrismaConfig, i18nConfig],',
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
- 'COPY packages/db-prisma/package.json packages/db-prisma/package.json',
231
+ packageAnchor,
183
232
  'COPY packages/logger/package.json packages/logger/package.json',
184
233
  );
185
- content = ensureLineAfter(
186
- content,
187
- 'COPY packages/db-prisma packages/db-prisma',
188
- 'COPY packages/logger packages/logger',
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(
@@ -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',
@@ -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\` (or your configured path)
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,6 @@
1
+ # {{MODULE_LABEL}}
2
+
3
+ - Id: `{{MODULE_ID}}`
4
+ - Category: `{{MODULE_CATEGORY}}`
5
+ - Status: {{MODULE_STATUS}}
6
+
@@ -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
+
@@ -0,0 +1,4 @@
1
+ ## Status
2
+
3
+ Implemented and applied by `create-forgeon add db-prisma`.
4
+
@@ -17,9 +17,9 @@ export function setupSwagger(app: INestApplication, config: SwaggerConfigService
17
17
  );
18
18
 
19
19
  SwaggerModule.setup(config.path, app, document, {
20
+ useGlobalPrefix: true,
20
21
  swaggerOptions: {
21
22
  persistAuthorization: true,
22
23
  },
23
24
  });
24
25
  }
25
-