create-forgeon 0.2.2 → 0.2.3
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/bin/create-forgeon.mjs +9 -6
- package/package.json +1 -1
- package/src/cli/add-help.mjs +10 -6
- package/src/cli/help.mjs +13 -9
- package/src/integrations/flow.mjs +118 -0
- package/src/modules/db-prisma.mjs +28 -162
- package/src/modules/executor.test.mjs +97 -10
- package/src/modules/i18n.mjs +10 -134
- package/src/modules/jwt-auth.mjs +40 -278
- package/src/modules/logger.mjs +10 -118
- package/src/modules/shared/patch-utils.mjs +162 -0
- package/src/modules/swagger.mjs +10 -121
- package/src/modules/sync-integrations.mjs +269 -0
- package/src/run-add-module.mjs +8 -42
- package/src/run-scan-integrations.mjs +93 -0
- package/templates/base/docs/AI/ARCHITECTURE.md +17 -0
- package/templates/base/docs/AI/MODULE_SPEC.md +4 -0
- package/templates/base/package.json +0 -3
- package/templates/base/scripts/forgeon-sync-integrations.mjs +44 -241
package/src/modules/i18n.mjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { copyRecursive, writeJson } from '../utils/fs.mjs';
|
|
4
|
+
import {
|
|
5
|
+
ensureBuildSteps,
|
|
6
|
+
ensureDependency,
|
|
7
|
+
ensureLineAfter,
|
|
8
|
+
ensureLineBefore,
|
|
9
|
+
ensureLoadItem,
|
|
10
|
+
ensureScript,
|
|
11
|
+
ensureValidatorSchema,
|
|
12
|
+
upsertEnvLines,
|
|
13
|
+
} from './shared/patch-utils.mjs';
|
|
4
14
|
|
|
5
15
|
function copyFromBase(packageRoot, targetRoot, relativePath) {
|
|
6
16
|
const source = path.join(packageRoot, 'templates', 'base', relativePath);
|
|
@@ -20,140 +30,6 @@ function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
|
20
30
|
copyRecursive(source, destination);
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
function ensureDependency(packageJson, name, version) {
|
|
24
|
-
if (!packageJson.dependencies) {
|
|
25
|
-
packageJson.dependencies = {};
|
|
26
|
-
}
|
|
27
|
-
packageJson.dependencies[name] = version;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function ensureScript(packageJson, name, command) {
|
|
31
|
-
if (!packageJson.scripts) {
|
|
32
|
-
packageJson.scripts = {};
|
|
33
|
-
}
|
|
34
|
-
packageJson.scripts[name] = command;
|
|
35
|
-
}
|
|
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
|
-
|
|
62
|
-
function upsertEnvLines(filePath, lines) {
|
|
63
|
-
let content = '';
|
|
64
|
-
if (fs.existsSync(filePath)) {
|
|
65
|
-
content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const keys = new Set(
|
|
69
|
-
content
|
|
70
|
-
.split('\n')
|
|
71
|
-
.filter(Boolean)
|
|
72
|
-
.map((line) => line.split('=')[0]),
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const append = [];
|
|
76
|
-
for (const line of lines) {
|
|
77
|
-
const key = line.split('=')[0];
|
|
78
|
-
if (!keys.has(key)) {
|
|
79
|
-
append.push(line);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const next =
|
|
84
|
-
append.length > 0 ? `${content.trimEnd()}\n${append.join('\n')}\n` : `${content.trimEnd()}\n`;
|
|
85
|
-
fs.writeFileSync(filePath, next.replace(/^\n/, ''), 'utf8');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function ensureLineAfter(content, anchorLine, lineToInsert) {
|
|
89
|
-
if (content.includes(lineToInsert)) {
|
|
90
|
-
return content;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const index = content.indexOf(anchorLine);
|
|
94
|
-
if (index < 0) {
|
|
95
|
-
return `${content.trimEnd()}\n${lineToInsert}\n`;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const insertAt = index + anchorLine.length;
|
|
99
|
-
return `${content.slice(0, insertAt)}\n${lineToInsert}${content.slice(insertAt)}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function ensureLineBefore(content, anchorLine, lineToInsert) {
|
|
103
|
-
if (content.includes(lineToInsert)) {
|
|
104
|
-
return content;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const index = content.indexOf(anchorLine);
|
|
108
|
-
if (index < 0) {
|
|
109
|
-
return `${content.trimEnd()}\n${lineToInsert}\n`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
|
|
113
|
-
}
|
|
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
|
-
|
|
157
33
|
function patchApiDockerfile(targetRoot) {
|
|
158
34
|
const dockerfilePath = path.join(targetRoot, 'apps', 'api', 'Dockerfile');
|
|
159
35
|
if (!fs.existsSync(dockerfilePath)) {
|
package/src/modules/jwt-auth.mjs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { copyRecursive, writeJson } from '../utils/fs.mjs';
|
|
4
|
+
import {
|
|
5
|
+
ensureBuildSteps,
|
|
6
|
+
ensureDependency,
|
|
7
|
+
ensureLineAfter,
|
|
8
|
+
ensureLineBefore,
|
|
9
|
+
ensureLoadItem,
|
|
10
|
+
ensureValidatorSchema,
|
|
11
|
+
upsertEnvLines,
|
|
12
|
+
} from './shared/patch-utils.mjs';
|
|
4
13
|
|
|
5
14
|
function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
6
15
|
const source = path.join(packageRoot, 'templates', 'module-presets', 'jwt-auth', relativePath);
|
|
@@ -12,177 +21,6 @@ function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
|
12
21
|
copyRecursive(source, destination);
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
function ensureDependency(packageJson, name, version) {
|
|
16
|
-
if (!packageJson.dependencies) {
|
|
17
|
-
packageJson.dependencies = {};
|
|
18
|
-
}
|
|
19
|
-
packageJson.dependencies[name] = version;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function ensureBuildSteps(packageJson, scriptName, requiredCommands) {
|
|
23
|
-
if (!packageJson.scripts) {
|
|
24
|
-
packageJson.scripts = {};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const current = packageJson.scripts[scriptName];
|
|
28
|
-
const steps =
|
|
29
|
-
typeof current === 'string' && current.trim().length > 0
|
|
30
|
-
? current
|
|
31
|
-
.split('&&')
|
|
32
|
-
.map((item) => item.trim())
|
|
33
|
-
.filter(Boolean)
|
|
34
|
-
: [];
|
|
35
|
-
|
|
36
|
-
for (const command of requiredCommands) {
|
|
37
|
-
if (!steps.includes(command)) {
|
|
38
|
-
steps.push(command);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (steps.length > 0) {
|
|
43
|
-
packageJson.scripts[scriptName] = steps.join(' && ');
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function ensureLineAfter(content, anchorLine, lineToInsert) {
|
|
48
|
-
if (content.includes(lineToInsert)) {
|
|
49
|
-
return content;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const index = content.indexOf(anchorLine);
|
|
53
|
-
if (index < 0) {
|
|
54
|
-
return `${content.trimEnd()}\n${lineToInsert}\n`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const insertAt = index + anchorLine.length;
|
|
58
|
-
return `${content.slice(0, insertAt)}\n${lineToInsert}${content.slice(insertAt)}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function ensureLineBefore(content, anchorLine, lineToInsert) {
|
|
62
|
-
if (content.includes(lineToInsert)) {
|
|
63
|
-
return content;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const index = content.indexOf(anchorLine);
|
|
67
|
-
if (index < 0) {
|
|
68
|
-
return `${content.trimEnd()}\n${lineToInsert}\n`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
|
|
72
|
-
}
|
|
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
|
-
|
|
116
|
-
function upsertEnvLines(filePath, lines) {
|
|
117
|
-
let content = '';
|
|
118
|
-
if (fs.existsSync(filePath)) {
|
|
119
|
-
content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const keys = new Set(
|
|
123
|
-
content
|
|
124
|
-
.split('\n')
|
|
125
|
-
.filter(Boolean)
|
|
126
|
-
.map((line) => line.split('=')[0]),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const append = [];
|
|
130
|
-
for (const line of lines) {
|
|
131
|
-
const key = line.split('=')[0];
|
|
132
|
-
if (!keys.has(key)) {
|
|
133
|
-
append.push(line);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const next =
|
|
138
|
-
append.length > 0 ? `${content.trimEnd()}\n${append.join('\n')}\n` : `${content.trimEnd()}\n`;
|
|
139
|
-
fs.writeFileSync(filePath, next.replace(/^\n/, ''), 'utf8');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function detectDbAdapter(targetRoot) {
|
|
143
|
-
const apiPackagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
|
|
144
|
-
let deps = {};
|
|
145
|
-
if (fs.existsSync(apiPackagePath)) {
|
|
146
|
-
const packageJson = JSON.parse(fs.readFileSync(apiPackagePath, 'utf8'));
|
|
147
|
-
deps = {
|
|
148
|
-
...(packageJson.dependencies ?? {}),
|
|
149
|
-
...(packageJson.devDependencies ?? {}),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
deps['@forgeon/db-prisma'] ||
|
|
155
|
-
fs.existsSync(path.join(targetRoot, 'packages', 'db-prisma', 'package.json'))
|
|
156
|
-
) {
|
|
157
|
-
return { id: 'db-prisma', supported: true, tokenStore: 'prisma' };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const dbDeps = Object.keys(deps).filter((name) => name.startsWith('@forgeon/db-'));
|
|
161
|
-
if (dbDeps.length > 0) {
|
|
162
|
-
return { id: dbDeps[0], supported: false, tokenStore: 'none' };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const packagesPath = path.join(targetRoot, 'packages');
|
|
166
|
-
if (fs.existsSync(packagesPath)) {
|
|
167
|
-
const localDbPackages = fs
|
|
168
|
-
.readdirSync(packagesPath, { withFileTypes: true })
|
|
169
|
-
.filter((entry) => entry.isDirectory() && entry.name.startsWith('db-'))
|
|
170
|
-
.map((entry) => entry.name);
|
|
171
|
-
if (localDbPackages.includes('db-prisma')) {
|
|
172
|
-
return { id: 'db-prisma', supported: true, tokenStore: 'prisma' };
|
|
173
|
-
}
|
|
174
|
-
if (localDbPackages.length > 0) {
|
|
175
|
-
return { id: `@forgeon/${localDbPackages[0]}`, supported: false, tokenStore: 'none' };
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function printDbWarning(message) {
|
|
183
|
-
console.error(`\x1b[31m[create-forgeon add jwt-auth] ${message}\x1b[0m`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
24
|
function patchApiPackage(targetRoot) {
|
|
187
25
|
const packagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
|
|
188
26
|
if (!fs.existsSync(packagePath)) {
|
|
@@ -201,21 +39,19 @@ function patchApiPackage(targetRoot) {
|
|
|
201
39
|
writeJson(packagePath, packageJson);
|
|
202
40
|
}
|
|
203
41
|
|
|
204
|
-
function patchAppModule(targetRoot
|
|
42
|
+
function patchAppModule(targetRoot) {
|
|
205
43
|
const filePath = path.join(targetRoot, 'apps', 'api', 'src', 'app.module.ts');
|
|
206
44
|
if (!fs.existsSync(filePath)) {
|
|
207
45
|
return;
|
|
208
46
|
}
|
|
209
47
|
|
|
210
|
-
const withPrismaStore = dbAdapter?.supported === true && dbAdapter?.id === 'db-prisma';
|
|
211
|
-
|
|
212
48
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
213
49
|
if (!content.includes("from '@forgeon/auth-api';")) {
|
|
214
50
|
if (content.includes("import { ForgeonI18nModule, i18nConfig, i18nEnvSchema } from '@forgeon/i18n';")) {
|
|
215
51
|
content = ensureLineAfter(
|
|
216
52
|
content,
|
|
217
53
|
"import { ForgeonI18nModule, i18nConfig, i18nEnvSchema } from '@forgeon/i18n';",
|
|
218
|
-
"import {
|
|
54
|
+
"import { authConfig, authEnvSchema, ForgeonAuthModule } from '@forgeon/auth-api';",
|
|
219
55
|
);
|
|
220
56
|
} else if (
|
|
221
57
|
content.includes("import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';")
|
|
@@ -223,7 +59,7 @@ function patchAppModule(targetRoot, dbAdapter) {
|
|
|
223
59
|
content = ensureLineAfter(
|
|
224
60
|
content,
|
|
225
61
|
"import { ForgeonLoggerModule, loggerConfig, loggerEnvSchema } from '@forgeon/logger';",
|
|
226
|
-
"import {
|
|
62
|
+
"import { authConfig, authEnvSchema, ForgeonAuthModule } from '@forgeon/auth-api';",
|
|
227
63
|
);
|
|
228
64
|
} else if (
|
|
229
65
|
content.includes("import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';")
|
|
@@ -231,7 +67,7 @@ function patchAppModule(targetRoot, dbAdapter) {
|
|
|
231
67
|
content = ensureLineAfter(
|
|
232
68
|
content,
|
|
233
69
|
"import { ForgeonSwaggerModule, swaggerConfig, swaggerEnvSchema } from '@forgeon/swagger';",
|
|
234
|
-
"import {
|
|
70
|
+
"import { authConfig, authEnvSchema, ForgeonAuthModule } from '@forgeon/auth-api';",
|
|
235
71
|
);
|
|
236
72
|
} else if (
|
|
237
73
|
content.includes("import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';")
|
|
@@ -239,38 +75,22 @@ function patchAppModule(targetRoot, dbAdapter) {
|
|
|
239
75
|
content = ensureLineAfter(
|
|
240
76
|
content,
|
|
241
77
|
"import { dbPrismaConfig, dbPrismaEnvSchema, DbPrismaModule } from '@forgeon/db-prisma';",
|
|
242
|
-
"import {
|
|
78
|
+
"import { authConfig, authEnvSchema, ForgeonAuthModule } from '@forgeon/auth-api';",
|
|
243
79
|
);
|
|
244
80
|
} else {
|
|
245
81
|
content = ensureLineAfter(
|
|
246
82
|
content,
|
|
247
83
|
"import { ConfigModule } from '@nestjs/config';",
|
|
248
|
-
"import {
|
|
84
|
+
"import { authConfig, authEnvSchema, ForgeonAuthModule } from '@forgeon/auth-api';",
|
|
249
85
|
);
|
|
250
86
|
}
|
|
251
87
|
}
|
|
252
88
|
|
|
253
|
-
if (withPrismaStore && !content.includes("./auth/prisma-auth-refresh-token.store")) {
|
|
254
|
-
content = ensureLineBefore(
|
|
255
|
-
content,
|
|
256
|
-
"import { HealthController } from './health/health.controller';",
|
|
257
|
-
"import { PrismaAuthRefreshTokenStore } from './auth/prisma-auth-refresh-token.store';",
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
89
|
content = ensureLoadItem(content, 'authConfig');
|
|
262
90
|
content = ensureValidatorSchema(content, 'authEnvSchema');
|
|
263
91
|
|
|
264
92
|
if (!content.includes('ForgeonAuthModule.register(')) {
|
|
265
|
-
const moduleBlock =
|
|
266
|
-
? ` ForgeonAuthModule.register({
|
|
267
|
-
imports: [DbPrismaModule],
|
|
268
|
-
refreshTokenStoreProvider: {
|
|
269
|
-
provide: AUTH_REFRESH_TOKEN_STORE,
|
|
270
|
-
useClass: PrismaAuthRefreshTokenStore,
|
|
271
|
-
},
|
|
272
|
-
}),`
|
|
273
|
-
: ` ForgeonAuthModule.register(),`;
|
|
93
|
+
const moduleBlock = ' ForgeonAuthModule.register(),';
|
|
274
94
|
|
|
275
95
|
if (content.includes(' ForgeonI18nModule.register({')) {
|
|
276
96
|
content = ensureLineBefore(content, ' ForgeonI18nModule.register({', moduleBlock);
|
|
@@ -297,6 +117,7 @@ function patchHealthController(targetRoot) {
|
|
|
297
117
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
298
118
|
|
|
299
119
|
if (!content.includes("from '@forgeon/auth-api';")) {
|
|
120
|
+
const nestCommonImport = content.match(/import\s*\{[^}]*\}\s*from '@nestjs\/common';/m)?.[0];
|
|
300
121
|
if (content.includes("import { PrismaService } from '@forgeon/db-prisma';")) {
|
|
301
122
|
content = ensureLineAfter(
|
|
302
123
|
content,
|
|
@@ -306,7 +127,7 @@ function patchHealthController(targetRoot) {
|
|
|
306
127
|
} else {
|
|
307
128
|
content = ensureLineAfter(
|
|
308
129
|
content,
|
|
309
|
-
"import {
|
|
130
|
+
nestCommonImport ?? "import { Controller, Get } from '@nestjs/common';",
|
|
310
131
|
"import { AuthService } from '@forgeon/auth-api';",
|
|
311
132
|
);
|
|
312
133
|
}
|
|
@@ -323,6 +144,16 @@ function patchHealthController(targetRoot) {
|
|
|
323
144
|
private readonly authService: AuthService,
|
|
324
145
|
) {`;
|
|
325
146
|
content = content.replace(original, next);
|
|
147
|
+
} else {
|
|
148
|
+
const classAnchor = 'export class HealthController {';
|
|
149
|
+
if (content.includes(classAnchor)) {
|
|
150
|
+
content = content.replace(
|
|
151
|
+
classAnchor,
|
|
152
|
+
`${classAnchor}
|
|
153
|
+
constructor(private readonly authService: AuthService) {}
|
|
154
|
+
`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
326
157
|
}
|
|
327
158
|
}
|
|
328
159
|
|
|
@@ -339,7 +170,12 @@ function patchHealthController(targetRoot) {
|
|
|
339
170
|
const index = content.indexOf('private translate(');
|
|
340
171
|
content = `${content.slice(0, index).trimEnd()}\n\n${method}\n${content.slice(index)}`;
|
|
341
172
|
} else {
|
|
342
|
-
|
|
173
|
+
const classEnd = content.lastIndexOf('\n}');
|
|
174
|
+
if (classEnd >= 0) {
|
|
175
|
+
content = `${content.slice(0, classEnd).trimEnd()}\n\n${method}\n${content.slice(classEnd)}`;
|
|
176
|
+
} else {
|
|
177
|
+
content = `${content.trimEnd()}\n${method}\n`;
|
|
178
|
+
}
|
|
343
179
|
}
|
|
344
180
|
}
|
|
345
181
|
|
|
@@ -488,22 +324,17 @@ function patchCompose(targetRoot) {
|
|
|
488
324
|
fs.writeFileSync(composePath, `${content.trimEnd()}\n`, 'utf8');
|
|
489
325
|
}
|
|
490
326
|
|
|
491
|
-
function patchReadme(targetRoot
|
|
327
|
+
function patchReadme(targetRoot) {
|
|
492
328
|
const readmePath = path.join(targetRoot, 'README.md');
|
|
493
329
|
if (!fs.existsSync(readmePath)) {
|
|
494
330
|
return;
|
|
495
331
|
}
|
|
496
332
|
|
|
497
333
|
const persistenceSummary =
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
: '- refresh token persistence: disabled (no supported DB adapter found)';
|
|
501
|
-
const dbFollowUp =
|
|
502
|
-
dbAdapter?.supported && dbAdapter.id === 'db-prisma'
|
|
503
|
-
? '- migration: `apps/api/prisma/migrations/0002_auth_refresh_token_hash`'
|
|
504
|
-
: `- to enable persistence later:
|
|
334
|
+
'- refresh token persistence: disabled by default (stateless mode)';
|
|
335
|
+
const dbFollowUp = `- to enable persistence later:
|
|
505
336
|
1. install a DB module first (for now: \`create-forgeon add db-prisma --project .\`);
|
|
506
|
-
2. run \`
|
|
337
|
+
2. run \`pnpm forgeon:sync-integrations\` to auto-wire pair integrations.`;
|
|
507
338
|
|
|
508
339
|
const section = `## JWT Auth Module
|
|
509
340
|
|
|
@@ -547,86 +378,17 @@ Default routes:
|
|
|
547
378
|
fs.writeFileSync(readmePath, `${content.trimEnd()}\n`, 'utf8');
|
|
548
379
|
}
|
|
549
380
|
|
|
550
|
-
function patchPrismaSchema(targetRoot) {
|
|
551
|
-
const schemaPath = path.join(targetRoot, 'apps', 'api', 'prisma', 'schema.prisma');
|
|
552
|
-
if (!fs.existsSync(schemaPath)) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
let content = fs.readFileSync(schemaPath, 'utf8').replace(/\r\n/g, '\n');
|
|
557
|
-
if (!content.includes('refreshTokenHash')) {
|
|
558
|
-
content = content.replace(
|
|
559
|
-
/email\s+String\s+@unique/g,
|
|
560
|
-
'email String @unique\n refreshTokenHash String?',
|
|
561
|
-
);
|
|
562
|
-
fs.writeFileSync(schemaPath, `${content.trimEnd()}\n`, 'utf8');
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
function patchPrismaMigration(packageRoot, targetRoot) {
|
|
567
|
-
const migrationSource = path.join(
|
|
568
|
-
packageRoot,
|
|
569
|
-
'templates',
|
|
570
|
-
'module-presets',
|
|
571
|
-
'jwt-auth',
|
|
572
|
-
'apps',
|
|
573
|
-
'api',
|
|
574
|
-
'prisma',
|
|
575
|
-
'migrations',
|
|
576
|
-
'0002_auth_refresh_token_hash',
|
|
577
|
-
);
|
|
578
|
-
const migrationTarget = path.join(
|
|
579
|
-
targetRoot,
|
|
580
|
-
'apps',
|
|
581
|
-
'api',
|
|
582
|
-
'prisma',
|
|
583
|
-
'migrations',
|
|
584
|
-
'0002_auth_refresh_token_hash',
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
if (!fs.existsSync(migrationTarget) && fs.existsSync(migrationSource)) {
|
|
588
|
-
copyRecursive(migrationSource, migrationTarget);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
381
|
export function applyJwtAuthModule({ packageRoot, targetRoot }) {
|
|
593
|
-
const dbAdapter = detectDbAdapter(targetRoot);
|
|
594
|
-
const supportsPrismaStore = dbAdapter?.supported === true && dbAdapter?.id === 'db-prisma';
|
|
595
|
-
|
|
596
382
|
copyFromPreset(packageRoot, targetRoot, path.join('packages', 'auth-contracts'));
|
|
597
383
|
copyFromPreset(packageRoot, targetRoot, path.join('packages', 'auth-api'));
|
|
598
384
|
|
|
599
|
-
const swaggerPackagePath = path.join(targetRoot, 'packages', 'swagger', 'package.json');
|
|
600
|
-
const authApiPackagePath = path.join(targetRoot, 'packages', 'auth-api', 'package.json');
|
|
601
|
-
if (fs.existsSync(swaggerPackagePath) && fs.existsSync(authApiPackagePath)) {
|
|
602
|
-
const authApiPackage = JSON.parse(fs.readFileSync(authApiPackagePath, 'utf8'));
|
|
603
|
-
ensureDependency(authApiPackage, '@nestjs/swagger', '^11.2.0');
|
|
604
|
-
writeJson(authApiPackagePath, authApiPackage);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if (supportsPrismaStore) {
|
|
608
|
-
copyFromPreset(
|
|
609
|
-
packageRoot,
|
|
610
|
-
targetRoot,
|
|
611
|
-
path.join('apps', 'api', 'src', 'auth', 'prisma-auth-refresh-token.store.ts'),
|
|
612
|
-
);
|
|
613
|
-
patchPrismaSchema(targetRoot);
|
|
614
|
-
patchPrismaMigration(packageRoot, targetRoot);
|
|
615
|
-
} else {
|
|
616
|
-
const detected = dbAdapter?.id ? `detected: ${dbAdapter.id}` : 'no DB adapter detected';
|
|
617
|
-
printDbWarning(
|
|
618
|
-
`jwt-auth installed without persistent refresh token store (${detected}). ` +
|
|
619
|
-
'Login/refresh works in stateless mode. Re-run add after supported DB module is installed.',
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
385
|
patchApiPackage(targetRoot);
|
|
624
|
-
patchAppModule(targetRoot
|
|
386
|
+
patchAppModule(targetRoot);
|
|
625
387
|
patchHealthController(targetRoot);
|
|
626
388
|
patchWebApp(targetRoot);
|
|
627
389
|
patchApiDockerfile(targetRoot);
|
|
628
390
|
patchCompose(targetRoot);
|
|
629
|
-
patchReadme(targetRoot
|
|
391
|
+
patchReadme(targetRoot);
|
|
630
392
|
|
|
631
393
|
upsertEnvLines(path.join(targetRoot, 'apps', 'api', '.env.example'), [
|
|
632
394
|
'JWT_ACCESS_SECRET=forgeon-access-secret-change-me',
|