create-forgeon 0.1.2 → 0.1.5

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.
Files changed (143) hide show
  1. package/README.md +19 -17
  2. package/bin/create-forgeon.mjs +22 -22
  3. package/package.json +1 -1
  4. package/src/cli/add-help.mjs +12 -12
  5. package/src/cli/add-options.mjs +54 -54
  6. package/src/cli/add-options.test.mjs +24 -24
  7. package/src/cli/help.mjs +20 -20
  8. package/src/cli/options.mjs +121 -121
  9. package/src/cli/options.test.mjs +41 -41
  10. package/src/cli/prompt-select.mjs +94 -94
  11. package/src/cli/prompt-select.test.mjs +148 -148
  12. package/src/constants.mjs +13 -13
  13. package/src/core/docs.mjs +128 -128
  14. package/src/core/docs.test.mjs +91 -91
  15. package/src/core/install.mjs +14 -14
  16. package/src/core/scaffold.mjs +48 -45
  17. package/src/core/validate.mjs +12 -12
  18. package/src/core/validate.test.mjs +73 -73
  19. package/src/databases/index.mjs +26 -26
  20. package/src/frameworks/index.mjs +32 -32
  21. package/src/infrastructure/proxy.mjs +12 -12
  22. package/src/modules/docs.mjs +70 -70
  23. package/src/modules/executor.mjs +39 -21
  24. package/src/modules/executor.test.mjs +95 -45
  25. package/src/modules/i18n.mjs +246 -0
  26. package/src/modules/registry.mjs +43 -35
  27. package/src/presets/i18n.mjs +211 -185
  28. package/src/presets/index.mjs +2 -2
  29. package/src/presets/proxy.mjs +32 -32
  30. package/src/run-add-module.mjs +47 -47
  31. package/src/run-create-forgeon.mjs +72 -72
  32. package/src/utils/fs.mjs +26 -26
  33. package/src/utils/values.mjs +20 -20
  34. package/templates/base/.dockerignore +7 -7
  35. package/templates/base/.editorconfig +11 -11
  36. package/templates/base/README.md +46 -46
  37. package/templates/base/apps/api/Dockerfile +24 -24
  38. package/templates/base/apps/api/package.json +39 -39
  39. package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +11 -11
  40. package/templates/base/apps/api/prisma/schema.prisma +14 -14
  41. package/templates/base/apps/api/prisma/seed.ts +19 -19
  42. package/templates/base/apps/api/src/app.module.ts +32 -32
  43. package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +5 -5
  44. package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +129 -129
  45. package/templates/base/apps/api/src/config/app.config.ts +12 -12
  46. package/templates/base/apps/api/src/health/health.controller.ts +30 -30
  47. package/templates/base/apps/api/src/main.ts +25 -25
  48. package/templates/base/apps/api/src/prisma/prisma.module.ts +8 -8
  49. package/templates/base/apps/api/src/prisma/prisma.service.ts +26 -26
  50. package/templates/base/apps/api/tsconfig.build.json +8 -8
  51. package/templates/base/apps/api/tsconfig.json +8 -8
  52. package/templates/base/apps/web/Dockerfile +12 -12
  53. package/templates/base/apps/web/index.html +11 -11
  54. package/templates/base/apps/web/package.json +21 -21
  55. package/templates/base/apps/web/src/App.tsx +35 -35
  56. package/templates/base/apps/web/src/main.tsx +8 -8
  57. package/templates/base/apps/web/src/styles.css +32 -32
  58. package/templates/base/apps/web/tsconfig.json +17 -17
  59. package/templates/base/apps/web/vite.config.ts +14 -14
  60. package/templates/base/docs/AI/ARCHITECTURE.md +37 -37
  61. package/templates/base/docs/AI/MODULE_SPEC.md +56 -56
  62. package/templates/base/docs/AI/PROJECT.md +31 -31
  63. package/templates/base/docs/AI/TASKS.md +57 -57
  64. package/templates/base/docs/README.md +6 -6
  65. package/templates/base/infra/caddy/Caddyfile +15 -15
  66. package/templates/base/infra/docker/.env.example +9 -9
  67. package/templates/base/infra/docker/caddy.Dockerfile +15 -15
  68. package/templates/base/infra/docker/compose.caddy.yml +44 -44
  69. package/templates/base/infra/docker/compose.nginx.yml +44 -44
  70. package/templates/base/infra/docker/compose.none.yml +37 -37
  71. package/templates/base/infra/docker/compose.yml +44 -44
  72. package/templates/base/infra/docker/nginx.Dockerfile +15 -15
  73. package/templates/base/infra/nginx/nginx.conf +31 -31
  74. package/templates/base/package.json +23 -23
  75. package/templates/base/packages/core/README.md +2 -2
  76. package/templates/base/packages/core/package.json +13 -13
  77. package/templates/base/packages/core/tsconfig.json +7 -7
  78. package/templates/base/packages/i18n/package.json +18 -18
  79. package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +45 -45
  80. package/templates/base/packages/i18n/tsconfig.json +8 -8
  81. package/templates/base/pnpm-workspace.yaml +2 -2
  82. package/templates/base/resources/i18n/en/common.json +4 -4
  83. package/templates/base/resources/i18n/en/validation.json +2 -2
  84. package/templates/base/resources/i18n/uk/common.json +4 -4
  85. package/templates/base/resources/i18n/uk/validation.json +2 -2
  86. package/templates/base/tsconfig.base.json +16 -16
  87. package/templates/docs-fragments/AI_ARCHITECTURE/00_title.md +1 -1
  88. package/templates/docs-fragments/AI_ARCHITECTURE/10_layout_base.md +6 -6
  89. package/templates/docs-fragments/AI_ARCHITECTURE/11_layout_infra.md +1 -1
  90. package/templates/docs-fragments/AI_ARCHITECTURE/12_layout_i18n_resources.md +1 -1
  91. package/templates/docs-fragments/AI_ARCHITECTURE/20_env_base.md +4 -4
  92. package/templates/docs-fragments/AI_ARCHITECTURE/21_env_i18n.md +3 -3
  93. package/templates/docs-fragments/AI_ARCHITECTURE/30_default_db.md +7 -7
  94. package/templates/docs-fragments/AI_ARCHITECTURE/31_docker_runtime.md +5 -5
  95. package/templates/docs-fragments/AI_ARCHITECTURE/32_scope_freeze.md +5 -5
  96. package/templates/docs-fragments/AI_ARCHITECTURE/40_docs_generation.md +9 -9
  97. package/templates/docs-fragments/AI_ARCHITECTURE/50_extension_points.md +8 -8
  98. package/templates/docs-fragments/AI_PROJECT/00_title.md +1 -1
  99. package/templates/docs-fragments/AI_PROJECT/10_what_is.md +3 -3
  100. package/templates/docs-fragments/AI_PROJECT/20_structure_base.md +5 -5
  101. package/templates/docs-fragments/AI_PROJECT/21_structure_i18n.md +2 -0
  102. package/templates/docs-fragments/AI_PROJECT/22_structure_docker.md +1 -1
  103. package/templates/docs-fragments/AI_PROJECT/23_structure_docs.md +1 -1
  104. package/templates/docs-fragments/AI_PROJECT/30_run_dev.md +8 -8
  105. package/templates/docs-fragments/AI_PROJECT/31_run_docker.md +5 -5
  106. package/templates/docs-fragments/AI_PROJECT/32_proxy_notes.md +5 -5
  107. package/templates/docs-fragments/AI_PROJECT/32_proxy_notes_none.md +5 -5
  108. package/templates/docs-fragments/AI_PROJECT/33_i18n_notes.md +2 -0
  109. package/templates/docs-fragments/AI_PROJECT/40_change_boundaries_base.md +3 -3
  110. package/templates/docs-fragments/AI_PROJECT/41_change_boundaries_docker.md +1 -1
  111. package/templates/docs-fragments/README/00_title.md +3 -3
  112. package/templates/docs-fragments/README/10_stack.md +8 -8
  113. package/templates/docs-fragments/README/20_quick_start_dev_intro.md +6 -6
  114. package/templates/docs-fragments/README/21_quick_start_dev_db_docker.md +4 -4
  115. package/templates/docs-fragments/README/21_quick_start_dev_db_local.md +1 -1
  116. package/templates/docs-fragments/README/22_quick_start_dev_outro.md +7 -7
  117. package/templates/docs-fragments/README/30_quick_start_docker.md +7 -7
  118. package/templates/docs-fragments/README/30_quick_start_docker_none.md +9 -9
  119. package/templates/docs-fragments/README/31_proxy_preset_caddy.md +9 -9
  120. package/templates/docs-fragments/README/31_proxy_preset_nginx.md +8 -8
  121. package/templates/docs-fragments/README/31_proxy_preset_none.md +6 -6
  122. package/templates/docs-fragments/README/32_prisma_container_start.md +5 -5
  123. package/templates/docs-fragments/README/40_i18n.md +14 -8
  124. package/templates/docs-fragments/README/90_next_steps.md +7 -7
  125. package/templates/module-fragments/i18n/00_title.md +5 -0
  126. package/templates/module-fragments/i18n/10_overview.md +9 -0
  127. package/templates/module-fragments/i18n/20_scope.md +7 -0
  128. package/templates/module-fragments/i18n/90_status_implemented.md +3 -0
  129. package/templates/module-fragments/jwt-auth/00_title.md +1 -1
  130. package/templates/module-fragments/jwt-auth/10_overview.md +6 -6
  131. package/templates/module-fragments/jwt-auth/20_scope.md +7 -7
  132. package/templates/module-fragments/jwt-auth/90_status_planned.md +3 -3
  133. package/templates/module-fragments/queue/00_title.md +1 -1
  134. package/templates/module-fragments/queue/10_overview.md +6 -6
  135. package/templates/module-fragments/queue/20_scope.md +7 -7
  136. package/templates/module-fragments/queue/90_status_planned.md +3 -3
  137. package/templates/module-presets/i18n/apps/web/src/App.tsx +61 -0
  138. package/templates/module-presets/i18n/packages/i18n-contracts/package.json +14 -0
  139. package/templates/module-presets/i18n/packages/i18n-contracts/src/index.ts +7 -0
  140. package/templates/module-presets/i18n/packages/i18n-contracts/tsconfig.json +8 -0
  141. package/templates/module-presets/i18n/packages/i18n-web/package.json +17 -0
  142. package/templates/module-presets/i18n/packages/i18n-web/src/index.ts +50 -0
  143. package/templates/module-presets/i18n/packages/i18n-web/tsconfig.json +8 -0
@@ -1,203 +1,229 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { removeIfExists, writeJson } from '../utils/fs.mjs';
4
-
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { removeIfExists, writeJson } from '../utils/fs.mjs';
4
+
5
5
  export function applyI18nDisabled(targetRoot) {
6
6
  removeIfExists(path.join(targetRoot, 'packages', 'i18n'));
7
+ removeIfExists(path.join(targetRoot, 'packages', 'i18n-contracts'));
8
+ removeIfExists(path.join(targetRoot, 'packages', 'i18n-web'));
7
9
  removeIfExists(path.join(targetRoot, 'resources', 'i18n'));
8
-
9
- const apiPackagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
10
- if (fs.existsSync(apiPackagePath)) {
11
- const apiPackage = JSON.parse(fs.readFileSync(apiPackagePath, 'utf8'));
12
-
13
- if (apiPackage.scripts) {
14
- delete apiPackage.scripts.predev;
15
- }
16
-
10
+
11
+ const apiPackagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
12
+ if (fs.existsSync(apiPackagePath)) {
13
+ const apiPackage = JSON.parse(fs.readFileSync(apiPackagePath, 'utf8'));
14
+
15
+ if (apiPackage.scripts) {
16
+ delete apiPackage.scripts.predev;
17
+ }
18
+
17
19
  if (apiPackage.dependencies) {
18
20
  delete apiPackage.dependencies['@forgeon/i18n'];
21
+ delete apiPackage.dependencies['@forgeon/i18n-contracts'];
19
22
  delete apiPackage.dependencies['nestjs-i18n'];
20
23
  }
21
-
22
- writeJson(apiPackagePath, apiPackage);
23
- }
24
-
25
- const apiDockerfile = path.join(targetRoot, 'apps', 'api', 'Dockerfile');
26
- if (fs.existsSync(apiDockerfile)) {
27
- let content = fs.readFileSync(apiDockerfile, 'utf8');
24
+
25
+ writeJson(apiPackagePath, apiPackage);
26
+ }
27
+
28
+ const apiDockerfile = path.join(targetRoot, 'apps', 'api', 'Dockerfile');
29
+ if (fs.existsSync(apiDockerfile)) {
30
+ let content = fs.readFileSync(apiDockerfile, 'utf8');
28
31
  content = content
29
32
  .replace(/^COPY packages\/i18n\/package\.json packages\/i18n\/package\.json\r?\n/gm, '')
33
+ .replace(
34
+ /^COPY packages\/i18n-contracts\/package\.json packages\/i18n-contracts\/package\.json\r?\n/gm,
35
+ '',
36
+ )
30
37
  .replace(/^COPY packages\/i18n packages\/i18n\r?\n/gm, '')
31
- .replace(/^RUN pnpm --filter @forgeon\/i18n build\r?\n/gm, '');
38
+ .replace(/^COPY packages\/i18n-contracts packages\/i18n-contracts\r?\n/gm, '')
39
+ .replace(/^RUN pnpm --filter @forgeon\/i18n build\r?\n/gm, '')
40
+ .replace(/^RUN pnpm --filter @forgeon\/i18n-contracts build\r?\n/gm, '');
32
41
  fs.writeFileSync(apiDockerfile, content, 'utf8');
33
42
  }
34
43
 
35
- const appModulePath = path.join(targetRoot, 'apps', 'api', 'src', 'app.module.ts');
36
- fs.writeFileSync(
37
- appModulePath,
38
- `import { Module } from '@nestjs/common';
39
- import { ConfigModule } from '@nestjs/config';
40
- import appConfig from './config/app.config';
41
- import { HealthController } from './health/health.controller';
42
- import { PrismaModule } from './prisma/prisma.module';
43
- import { AppExceptionFilter } from './common/filters/app-exception.filter';
44
-
45
- @Module({
46
- imports: [
47
- ConfigModule.forRoot({
48
- isGlobal: true,
49
- load: [appConfig],
50
- envFilePath: '.env',
51
- }),
52
- PrismaModule,
53
- ],
54
- controllers: [HealthController],
55
- providers: [AppExceptionFilter],
56
- })
57
- export class AppModule {}
58
- `,
59
- 'utf8',
60
- );
61
-
62
- const healthControllerPath = path.join(
63
- targetRoot,
64
- 'apps',
65
- 'api',
66
- 'src',
67
- 'health',
68
- 'health.controller.ts',
69
- );
70
- fs.writeFileSync(
71
- healthControllerPath,
72
- `import { Controller, Get, Query } from '@nestjs/common';
73
- import { EchoQueryDto } from '../common/dto/echo-query.dto';
74
-
75
- @Controller('health')
76
- export class HealthController {
77
- @Get()
78
- getHealth(@Query('lang') _lang?: string) {
79
- return {
80
- status: 'ok',
81
- message: 'OK',
82
- };
83
- }
84
-
85
- @Get('echo')
86
- getEcho(@Query() query: EchoQueryDto) {
87
- return { value: query.value };
88
- }
89
- }
90
- `,
91
- 'utf8',
92
- );
93
-
94
- const filterPath = path.join(
95
- targetRoot,
96
- 'apps',
97
- 'api',
98
- 'src',
99
- 'common',
100
- 'filters',
101
- 'app-exception.filter.ts',
102
- );
103
- fs.writeFileSync(
104
- filterPath,
105
- `import {
106
- ArgumentsHost,
107
- Catch,
108
- ExceptionFilter,
109
- HttpException,
110
- HttpStatus,
111
- Injectable,
112
- } from '@nestjs/common';
113
- import { Response } from 'express';
114
-
115
- @Injectable()
116
- @Catch()
117
- export class AppExceptionFilter implements ExceptionFilter {
118
- catch(exception: unknown, host: ArgumentsHost): void {
119
- const context = host.switchToHttp();
120
- const response = context.getResponse<Response>();
121
-
122
- const status =
123
- exception instanceof HttpException
124
- ? exception.getStatus()
125
- : HttpStatus.INTERNAL_SERVER_ERROR;
44
+ const webPackagePath = path.join(targetRoot, 'apps', 'web', 'package.json');
45
+ if (fs.existsSync(webPackagePath)) {
46
+ const webPackage = JSON.parse(fs.readFileSync(webPackagePath, 'utf8'));
126
47
 
127
- const payload =
128
- exception instanceof HttpException
129
- ? exception.getResponse()
130
- : { message: 'Internal server error' };
131
-
132
- const message =
133
- typeof payload === 'object' && payload !== null && 'message' in payload
134
- ? Array.isArray((payload as { message?: unknown }).message)
135
- ? String((payload as { message: unknown[] }).message[0] ?? 'Internal server error')
136
- : String((payload as { message?: unknown }).message ?? 'Internal server error')
137
- : typeof payload === 'string'
138
- ? payload
139
- : 'Internal server error';
140
-
141
- response.status(status).json({
142
- error: {
143
- code: this.resolveCode(status),
144
- message,
145
- },
146
- });
147
- }
148
-
149
- private resolveCode(status: number): string {
150
- switch (status) {
151
- case HttpStatus.BAD_REQUEST:
152
- return 'validation_error';
153
- case HttpStatus.UNAUTHORIZED:
154
- return 'unauthorized';
155
- case HttpStatus.FORBIDDEN:
156
- return 'forbidden';
157
- case HttpStatus.NOT_FOUND:
158
- return 'not_found';
159
- case HttpStatus.CONFLICT:
160
- return 'conflict';
161
- default:
162
- return 'internal_error';
48
+ if (webPackage.scripts) {
49
+ delete webPackage.scripts.predev;
50
+ delete webPackage.scripts.prebuild;
163
51
  }
164
- }
165
- }
166
- `,
167
- 'utf8',
168
- );
169
-
170
- const appConfigPath = path.join(targetRoot, 'apps', 'api', 'src', 'config', 'app.config.ts');
171
- fs.writeFileSync(
172
- appConfigPath,
173
- `import { registerAs } from '@nestjs/config';
174
52
 
175
- export default registerAs('app', () => ({
176
- port: Number(process.env.PORT ?? 3000),
177
- }));
178
- `,
179
- 'utf8',
180
- );
181
- }
182
-
183
- export function patchDockerEnvForI18n(targetRoot, i18nEnabled) {
184
- const dockerEnvPath = path.join(targetRoot, 'infra', 'docker', '.env.example');
185
- if (fs.existsSync(dockerEnvPath) && !i18nEnabled) {
186
- const content = fs
187
- .readFileSync(dockerEnvPath, 'utf8')
188
- .replace(/^I18N_ENABLED=.*\r?\n/gm, '')
189
- .replace(/^I18N_DEFAULT_LANG=.*\r?\n/gm, '')
190
- .replace(/^I18N_FALLBACK_LANG=.*\r?\n/gm, '');
191
- fs.writeFileSync(dockerEnvPath, content.trimEnd() + '\n', 'utf8');
192
- }
53
+ if (webPackage.dependencies) {
54
+ delete webPackage.dependencies['@forgeon/i18n-contracts'];
55
+ delete webPackage.dependencies['@forgeon/i18n-web'];
56
+ }
193
57
 
194
- const composePath = path.join(targetRoot, 'infra', 'docker', 'compose.yml');
195
- if (fs.existsSync(composePath) && !i18nEnabled) {
196
- const content = fs
197
- .readFileSync(composePath, 'utf8')
198
- .replace(/^\s+I18N_ENABLED:.*\r?\n/gm, '')
199
- .replace(/^\s+I18N_DEFAULT_LANG:.*\r?\n/gm, '')
200
- .replace(/^\s+I18N_FALLBACK_LANG:.*\r?\n/gm, '');
201
- fs.writeFileSync(composePath, content, 'utf8');
58
+ writeJson(webPackagePath, webPackage);
202
59
  }
203
- }
60
+
61
+ const appModulePath = path.join(targetRoot, 'apps', 'api', 'src', 'app.module.ts');
62
+ fs.writeFileSync(
63
+ appModulePath,
64
+ `import { Module } from '@nestjs/common';
65
+ import { ConfigModule } from '@nestjs/config';
66
+ import appConfig from './config/app.config';
67
+ import { HealthController } from './health/health.controller';
68
+ import { PrismaModule } from './prisma/prisma.module';
69
+ import { AppExceptionFilter } from './common/filters/app-exception.filter';
70
+
71
+ @Module({
72
+ imports: [
73
+ ConfigModule.forRoot({
74
+ isGlobal: true,
75
+ load: [appConfig],
76
+ envFilePath: '.env',
77
+ }),
78
+ PrismaModule,
79
+ ],
80
+ controllers: [HealthController],
81
+ providers: [AppExceptionFilter],
82
+ })
83
+ export class AppModule {}
84
+ `,
85
+ 'utf8',
86
+ );
87
+
88
+ const healthControllerPath = path.join(
89
+ targetRoot,
90
+ 'apps',
91
+ 'api',
92
+ 'src',
93
+ 'health',
94
+ 'health.controller.ts',
95
+ );
96
+ fs.writeFileSync(
97
+ healthControllerPath,
98
+ `import { Controller, Get, Query } from '@nestjs/common';
99
+ import { EchoQueryDto } from '../common/dto/echo-query.dto';
100
+
101
+ @Controller('health')
102
+ export class HealthController {
103
+ @Get()
104
+ getHealth(@Query('lang') _lang?: string) {
105
+ return {
106
+ status: 'ok',
107
+ message: 'OK',
108
+ };
109
+ }
110
+
111
+ @Get('echo')
112
+ getEcho(@Query() query: EchoQueryDto) {
113
+ return { value: query.value };
114
+ }
115
+ }
116
+ `,
117
+ 'utf8',
118
+ );
119
+
120
+ const filterPath = path.join(
121
+ targetRoot,
122
+ 'apps',
123
+ 'api',
124
+ 'src',
125
+ 'common',
126
+ 'filters',
127
+ 'app-exception.filter.ts',
128
+ );
129
+ fs.writeFileSync(
130
+ filterPath,
131
+ `import {
132
+ ArgumentsHost,
133
+ Catch,
134
+ ExceptionFilter,
135
+ HttpException,
136
+ HttpStatus,
137
+ Injectable,
138
+ } from '@nestjs/common';
139
+ import { Response } from 'express';
140
+
141
+ @Injectable()
142
+ @Catch()
143
+ export class AppExceptionFilter implements ExceptionFilter {
144
+ catch(exception: unknown, host: ArgumentsHost): void {
145
+ const context = host.switchToHttp();
146
+ const response = context.getResponse<Response>();
147
+
148
+ const status =
149
+ exception instanceof HttpException
150
+ ? exception.getStatus()
151
+ : HttpStatus.INTERNAL_SERVER_ERROR;
152
+
153
+ const payload =
154
+ exception instanceof HttpException
155
+ ? exception.getResponse()
156
+ : { message: 'Internal server error' };
157
+
158
+ const message =
159
+ typeof payload === 'object' && payload !== null && 'message' in payload
160
+ ? Array.isArray((payload as { message?: unknown }).message)
161
+ ? String((payload as { message: unknown[] }).message[0] ?? 'Internal server error')
162
+ : String((payload as { message?: unknown }).message ?? 'Internal server error')
163
+ : typeof payload === 'string'
164
+ ? payload
165
+ : 'Internal server error';
166
+
167
+ response.status(status).json({
168
+ error: {
169
+ code: this.resolveCode(status),
170
+ message,
171
+ },
172
+ });
173
+ }
174
+
175
+ private resolveCode(status: number): string {
176
+ switch (status) {
177
+ case HttpStatus.BAD_REQUEST:
178
+ return 'validation_error';
179
+ case HttpStatus.UNAUTHORIZED:
180
+ return 'unauthorized';
181
+ case HttpStatus.FORBIDDEN:
182
+ return 'forbidden';
183
+ case HttpStatus.NOT_FOUND:
184
+ return 'not_found';
185
+ case HttpStatus.CONFLICT:
186
+ return 'conflict';
187
+ default:
188
+ return 'internal_error';
189
+ }
190
+ }
191
+ }
192
+ `,
193
+ 'utf8',
194
+ );
195
+
196
+ const appConfigPath = path.join(targetRoot, 'apps', 'api', 'src', 'config', 'app.config.ts');
197
+ fs.writeFileSync(
198
+ appConfigPath,
199
+ `import { registerAs } from '@nestjs/config';
200
+
201
+ export default registerAs('app', () => ({
202
+ port: Number(process.env.PORT ?? 3000),
203
+ }));
204
+ `,
205
+ 'utf8',
206
+ );
207
+ }
208
+
209
+ export function patchDockerEnvForI18n(targetRoot, i18nEnabled) {
210
+ const dockerEnvPath = path.join(targetRoot, 'infra', 'docker', '.env.example');
211
+ if (fs.existsSync(dockerEnvPath) && !i18nEnabled) {
212
+ const content = fs
213
+ .readFileSync(dockerEnvPath, 'utf8')
214
+ .replace(/^I18N_ENABLED=.*\r?\n/gm, '')
215
+ .replace(/^I18N_DEFAULT_LANG=.*\r?\n/gm, '')
216
+ .replace(/^I18N_FALLBACK_LANG=.*\r?\n/gm, '');
217
+ fs.writeFileSync(dockerEnvPath, content.trimEnd() + '\n', 'utf8');
218
+ }
219
+
220
+ const composePath = path.join(targetRoot, 'infra', 'docker', 'compose.yml');
221
+ if (fs.existsSync(composePath) && !i18nEnabled) {
222
+ const content = fs
223
+ .readFileSync(composePath, 'utf8')
224
+ .replace(/^\s+I18N_ENABLED:.*\r?\n/gm, '')
225
+ .replace(/^\s+I18N_DEFAULT_LANG:.*\r?\n/gm, '')
226
+ .replace(/^\s+I18N_FALLBACK_LANG:.*\r?\n/gm, '');
227
+ fs.writeFileSync(composePath, content, 'utf8');
228
+ }
229
+ }
@@ -1,2 +1,2 @@
1
- export { applyProxyPreset } from './proxy.mjs';
2
- export { applyI18nDisabled, patchDockerEnvForI18n } from './i18n.mjs';
1
+ export { applyProxyPreset } from './proxy.mjs';
2
+ export { applyI18nDisabled, patchDockerEnvForI18n } from './i18n.mjs';
@@ -1,32 +1,32 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { removeIfExists } from '../utils/fs.mjs';
4
-
5
- export function applyProxyPreset(targetRoot, proxy) {
6
- const dockerDir = path.join(targetRoot, 'infra', 'docker');
7
- const composeTarget = path.join(dockerDir, 'compose.yml');
8
- const composeSource = path.join(dockerDir, `compose.${proxy}.yml`);
9
-
10
- if (!fs.existsSync(composeSource)) {
11
- throw new Error(`Missing proxy compose preset: ${composeSource}`);
12
- }
13
-
14
- fs.copyFileSync(composeSource, composeTarget);
15
-
16
- removeIfExists(path.join(dockerDir, 'compose.nginx.yml'));
17
- removeIfExists(path.join(dockerDir, 'compose.caddy.yml'));
18
- removeIfExists(path.join(dockerDir, 'compose.none.yml'));
19
-
20
- if (proxy === 'nginx') {
21
- removeIfExists(path.join(dockerDir, 'caddy.Dockerfile'));
22
- removeIfExists(path.join(targetRoot, 'infra', 'caddy'));
23
- } else if (proxy === 'caddy') {
24
- removeIfExists(path.join(dockerDir, 'nginx.Dockerfile'));
25
- removeIfExists(path.join(targetRoot, 'infra', 'nginx'));
26
- } else if (proxy === 'none') {
27
- removeIfExists(path.join(dockerDir, 'nginx.Dockerfile'));
28
- removeIfExists(path.join(dockerDir, 'caddy.Dockerfile'));
29
- removeIfExists(path.join(targetRoot, 'infra', 'nginx'));
30
- removeIfExists(path.join(targetRoot, 'infra', 'caddy'));
31
- }
32
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { removeIfExists } from '../utils/fs.mjs';
4
+
5
+ export function applyProxyPreset(targetRoot, proxy) {
6
+ const dockerDir = path.join(targetRoot, 'infra', 'docker');
7
+ const composeTarget = path.join(dockerDir, 'compose.yml');
8
+ const composeSource = path.join(dockerDir, `compose.${proxy}.yml`);
9
+
10
+ if (!fs.existsSync(composeSource)) {
11
+ throw new Error(`Missing proxy compose preset: ${composeSource}`);
12
+ }
13
+
14
+ fs.copyFileSync(composeSource, composeTarget);
15
+
16
+ removeIfExists(path.join(dockerDir, 'compose.nginx.yml'));
17
+ removeIfExists(path.join(dockerDir, 'compose.caddy.yml'));
18
+ removeIfExists(path.join(dockerDir, 'compose.none.yml'));
19
+
20
+ if (proxy === 'nginx') {
21
+ removeIfExists(path.join(dockerDir, 'caddy.Dockerfile'));
22
+ removeIfExists(path.join(targetRoot, 'infra', 'caddy'));
23
+ } else if (proxy === 'caddy') {
24
+ removeIfExists(path.join(dockerDir, 'nginx.Dockerfile'));
25
+ removeIfExists(path.join(targetRoot, 'infra', 'nginx'));
26
+ } else if (proxy === 'none') {
27
+ removeIfExists(path.join(dockerDir, 'nginx.Dockerfile'));
28
+ removeIfExists(path.join(dockerDir, 'caddy.Dockerfile'));
29
+ removeIfExists(path.join(targetRoot, 'infra', 'nginx'));
30
+ removeIfExists(path.join(targetRoot, 'infra', 'caddy'));
31
+ }
32
+ }
@@ -1,47 +1,47 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import { printAddHelp } from './cli/add-help.mjs';
4
- import { parseAddCliArgs } from './cli/add-options.mjs';
5
- import { addModule } from './modules/executor.mjs';
6
- import { listModulePresets } from './modules/registry.mjs';
7
-
8
- function printModuleList() {
9
- const modules = listModulePresets();
10
- console.log('Available modules:');
11
- for (const moduleItem of modules) {
12
- const status = moduleItem.implemented ? 'implemented' : 'planned';
13
- console.log(`- ${moduleItem.id} (${status}) - ${moduleItem.description}`);
14
- }
15
- }
16
-
17
- export async function runAddModule(argv = process.argv.slice(2)) {
18
- const options = parseAddCliArgs(argv);
19
-
20
- if (options.help) {
21
- printAddHelp();
22
- return;
23
- }
24
-
25
- if (options.list) {
26
- printModuleList();
27
- return;
28
- }
29
-
30
- if (!options.moduleId) {
31
- throw new Error('Module id is required. Use `create-forgeon add --list` to see available modules.');
32
- }
33
-
34
- const srcDir = path.dirname(fileURLToPath(import.meta.url));
35
- const packageRoot = path.resolve(srcDir, '..');
36
- const targetRoot = path.resolve(process.cwd(), options.project);
37
-
38
- const result = addModule({
39
- moduleId: options.moduleId,
40
- targetRoot,
41
- packageRoot,
42
- });
43
-
44
- console.log(result.message);
45
- console.log(`- module: ${result.preset.id}`);
46
- console.log(`- docs: ${result.docsPath}`);
47
- }
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { printAddHelp } from './cli/add-help.mjs';
4
+ import { parseAddCliArgs } from './cli/add-options.mjs';
5
+ import { addModule } from './modules/executor.mjs';
6
+ import { listModulePresets } from './modules/registry.mjs';
7
+
8
+ function printModuleList() {
9
+ const modules = listModulePresets();
10
+ console.log('Available modules:');
11
+ for (const moduleItem of modules) {
12
+ const status = moduleItem.implemented ? 'implemented' : 'planned';
13
+ console.log(`- ${moduleItem.id} (${status}) - ${moduleItem.description}`);
14
+ }
15
+ }
16
+
17
+ export async function runAddModule(argv = process.argv.slice(2)) {
18
+ const options = parseAddCliArgs(argv);
19
+
20
+ if (options.help) {
21
+ printAddHelp();
22
+ return;
23
+ }
24
+
25
+ if (options.list) {
26
+ printModuleList();
27
+ return;
28
+ }
29
+
30
+ if (!options.moduleId) {
31
+ throw new Error('Module id is required. Use `create-forgeon add --list` to see available modules.');
32
+ }
33
+
34
+ const srcDir = path.dirname(fileURLToPath(import.meta.url));
35
+ const packageRoot = path.resolve(srcDir, '..');
36
+ const targetRoot = path.resolve(process.cwd(), options.project);
37
+
38
+ const result = addModule({
39
+ moduleId: options.moduleId,
40
+ targetRoot,
41
+ packageRoot,
42
+ });
43
+
44
+ console.log(result.message);
45
+ console.log(`- module: ${result.preset.id}`);
46
+ console.log(`- docs: ${result.docsPath}`);
47
+ }