create-forgeon 0.1.34 → 0.1.36
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 +1 -0
- package/package.json +1 -1
- package/src/modules/db-prisma.mjs +401 -0
- package/src/modules/executor.mjs +4 -0
- package/src/modules/executor.test.mjs +585 -13
- package/src/modules/i18n.mjs +244 -22
- package/src/modules/jwt-auth.mjs +612 -0
- package/src/modules/logger.mjs +76 -27
- package/src/modules/registry.mjs +15 -7
- package/src/modules/swagger.mjs +12 -2
- 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-fragments/jwt-auth/20_scope.md +17 -7
- package/templates/module-fragments/jwt-auth/90_status_implemented.md +7 -0
- package/templates/module-presets/jwt-auth/apps/api/prisma/migrations/0002_auth_refresh_token_hash/migration.sql +3 -0
- package/templates/module-presets/jwt-auth/apps/api/src/auth/prisma-auth-refresh-token.store.ts +36 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/package.json +28 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-config.loader.ts +27 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-config.module.ts +8 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-config.service.ts +36 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-env.schema.ts +19 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-refresh-token.store.ts +23 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.controller.ts +71 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.service.ts +155 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.types.ts +6 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/index.ts +2 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/login.dto.ts +11 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/refresh.dto.ts +8 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/forgeon-auth.module.ts +47 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/index.ts +12 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/jwt-auth.guard.ts +5 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/src/jwt.strategy.ts +20 -0
- package/templates/module-presets/jwt-auth/packages/auth-api/tsconfig.json +9 -0
- package/templates/module-presets/jwt-auth/packages/auth-contracts/package.json +21 -0
- package/templates/module-presets/jwt-auth/packages/auth-contracts/src/index.ts +47 -0
- package/templates/module-presets/jwt-auth/packages/auth-contracts/tsconfig.json +9 -0
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
|
|