create-forgeon 0.2.7 → 0.2.8
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
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { addModule } from './executor.mjs';
|
|
8
|
-
import { syncIntegrations } from './sync-integrations.mjs';
|
|
8
|
+
import { scanIntegrations, syncIntegrations } from './sync-integrations.mjs';
|
|
9
9
|
import { scaffoldProject } from '../core/scaffold.mjs';
|
|
10
10
|
|
|
11
11
|
function mkTmp(prefix) {
|
|
@@ -1145,6 +1145,80 @@ describe('addModule', () => {
|
|
|
1145
1145
|
}
|
|
1146
1146
|
});
|
|
1147
1147
|
|
|
1148
|
+
it('detects and applies jwt-auth + rbac claims integration explicitly', () => {
|
|
1149
|
+
const targetRoot = mkTmp('forgeon-module-jwt-rbac-');
|
|
1150
|
+
const projectRoot = path.join(targetRoot, 'demo-jwt-rbac');
|
|
1151
|
+
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
1152
|
+
|
|
1153
|
+
try {
|
|
1154
|
+
scaffoldProject({
|
|
1155
|
+
templateRoot,
|
|
1156
|
+
packageRoot,
|
|
1157
|
+
targetRoot: projectRoot,
|
|
1158
|
+
projectName: 'demo-jwt-rbac',
|
|
1159
|
+
frontend: 'react',
|
|
1160
|
+
db: 'prisma',
|
|
1161
|
+
dbPrismaEnabled: false,
|
|
1162
|
+
i18nEnabled: false,
|
|
1163
|
+
proxy: 'caddy',
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
addModule({
|
|
1167
|
+
moduleId: 'rbac',
|
|
1168
|
+
targetRoot: projectRoot,
|
|
1169
|
+
packageRoot,
|
|
1170
|
+
});
|
|
1171
|
+
addModule({
|
|
1172
|
+
moduleId: 'jwt-auth',
|
|
1173
|
+
targetRoot: projectRoot,
|
|
1174
|
+
packageRoot,
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
const scan = scanIntegrations({
|
|
1178
|
+
targetRoot: projectRoot,
|
|
1179
|
+
relatedModuleId: 'jwt-auth',
|
|
1180
|
+
});
|
|
1181
|
+
assert.equal(scan.groups.some((group) => group.id === 'auth-rbac-claims'), true);
|
|
1182
|
+
|
|
1183
|
+
const syncResult = syncIntegrations({
|
|
1184
|
+
targetRoot: projectRoot,
|
|
1185
|
+
packageRoot,
|
|
1186
|
+
groupIds: ['auth-rbac-claims'],
|
|
1187
|
+
});
|
|
1188
|
+
const claimsPair = syncResult.summary.find((item) => item.id === 'auth-rbac-claims');
|
|
1189
|
+
assert.ok(claimsPair);
|
|
1190
|
+
assert.equal(claimsPair.result.applied, true);
|
|
1191
|
+
|
|
1192
|
+
const authContracts = fs.readFileSync(
|
|
1193
|
+
path.join(projectRoot, 'packages', 'auth-contracts', 'src', 'index.ts'),
|
|
1194
|
+
'utf8',
|
|
1195
|
+
);
|
|
1196
|
+
assert.match(authContracts, /permissions\?: string\[\];/);
|
|
1197
|
+
|
|
1198
|
+
const authService = fs.readFileSync(
|
|
1199
|
+
path.join(projectRoot, 'packages', 'auth-api', 'src', 'auth.service.ts'),
|
|
1200
|
+
'utf8',
|
|
1201
|
+
);
|
|
1202
|
+
assert.match(authService, /permissions: \['health\.rbac'\]/);
|
|
1203
|
+
assert.match(authService, /permissions: user\.permissions,/);
|
|
1204
|
+
assert.match(
|
|
1205
|
+
authService,
|
|
1206
|
+
/permissions: Array\.isArray\(payload\.permissions\) \? payload\.permissions : \[\],/,
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1209
|
+
const authController = fs.readFileSync(
|
|
1210
|
+
path.join(projectRoot, 'packages', 'auth-api', 'src', 'auth.controller.ts'),
|
|
1211
|
+
'utf8',
|
|
1212
|
+
);
|
|
1213
|
+
assert.match(
|
|
1214
|
+
authController,
|
|
1215
|
+
/permissions: Array\.isArray\(payload\.permissions\) \? payload\.permissions : \[\],/,
|
|
1216
|
+
);
|
|
1217
|
+
} finally {
|
|
1218
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1148
1222
|
it('applies logger then jwt-auth on db/i18n-disabled scaffold without breaking health controller syntax', () => {
|
|
1149
1223
|
const targetRoot = mkTmp('forgeon-module-jwt-nodb-noi18n-');
|
|
1150
1224
|
const projectRoot = path.join(targetRoot, 'demo-jwt-nodb-noi18n');
|
|
@@ -68,6 +68,29 @@ function isAuthPersistencePending(rootDir) {
|
|
|
68
68
|
return !(hasModuleWiring && hasSchema && hasStoreFile && hasMigration);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function isAuthRbacPending(rootDir) {
|
|
72
|
+
const authContractsPath = path.join(rootDir, 'packages', 'auth-contracts', 'src', 'index.ts');
|
|
73
|
+
const authServicePath = path.join(rootDir, 'packages', 'auth-api', 'src', 'auth.service.ts');
|
|
74
|
+
const authControllerPath = path.join(rootDir, 'packages', 'auth-api', 'src', 'auth.controller.ts');
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(authContractsPath) || !fs.existsSync(authServicePath) || !fs.existsSync(authControllerPath)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const authContracts = fs.readFileSync(authContractsPath, 'utf8');
|
|
81
|
+
const authService = fs.readFileSync(authServicePath, 'utf8');
|
|
82
|
+
const authController = fs.readFileSync(authControllerPath, 'utf8');
|
|
83
|
+
|
|
84
|
+
const hasContracts = authContracts.includes('permissions?: string[];');
|
|
85
|
+
const hasDemoClaims = authService.includes("permissions: ['health.rbac']");
|
|
86
|
+
const hasPayloadClaims = authService.includes('permissions: user.permissions,');
|
|
87
|
+
const hasRefreshClaims = authService.includes('permissions: Array.isArray(payload.permissions) ? payload.permissions : [],');
|
|
88
|
+
const hasControllerClaims =
|
|
89
|
+
authController.includes('permissions: Array.isArray(payload.permissions) ? payload.permissions : [],');
|
|
90
|
+
|
|
91
|
+
return !(hasContracts && hasDemoClaims && hasPayloadClaims && hasRefreshClaims && hasControllerClaims);
|
|
92
|
+
}
|
|
93
|
+
|
|
71
94
|
const INTEGRATION_GROUPS = [
|
|
72
95
|
{
|
|
73
96
|
id: 'auth-persistence',
|
|
@@ -83,6 +106,20 @@ const INTEGRATION_GROUPS = [
|
|
|
83
106
|
isPending: (rootDir) => isAuthPersistencePending(rootDir),
|
|
84
107
|
apply: syncJwtDbPrisma,
|
|
85
108
|
},
|
|
109
|
+
{
|
|
110
|
+
id: 'auth-rbac-claims',
|
|
111
|
+
title: 'Auth Claims Integration',
|
|
112
|
+
modules: ['jwt-auth', 'rbac'],
|
|
113
|
+
description: [
|
|
114
|
+
'Extend AuthUser with optional permissions in @forgeon/auth-contracts',
|
|
115
|
+
'Add demo RBAC claims to jwt-auth login and token payloads',
|
|
116
|
+
'Expose permissions in auth refresh and /me responses',
|
|
117
|
+
'Update JWT auth README note about RBAC demo claims',
|
|
118
|
+
],
|
|
119
|
+
isAvailable: (detected) => detected.jwtAuth && detected.rbac,
|
|
120
|
+
isPending: (rootDir) => isAuthRbacPending(rootDir),
|
|
121
|
+
apply: syncJwtRbacClaims,
|
|
122
|
+
},
|
|
86
123
|
];
|
|
87
124
|
|
|
88
125
|
function detectModules(rootDir) {
|
|
@@ -93,6 +130,9 @@ function detectModules(rootDir) {
|
|
|
93
130
|
jwtAuth:
|
|
94
131
|
fs.existsSync(path.join(rootDir, 'packages', 'auth-api', 'package.json')) ||
|
|
95
132
|
appModuleText.includes("from '@forgeon/auth-api'"),
|
|
133
|
+
rbac:
|
|
134
|
+
fs.existsSync(path.join(rootDir, 'packages', 'rbac', 'package.json')) ||
|
|
135
|
+
appModuleText.includes("from '@forgeon/rbac'"),
|
|
96
136
|
dbPrisma:
|
|
97
137
|
fs.existsSync(path.join(rootDir, 'packages', 'db-prisma', 'package.json')) ||
|
|
98
138
|
appModuleText.includes("from '@forgeon/db-prisma'"),
|
|
@@ -216,6 +256,109 @@ function syncJwtDbPrisma({ rootDir, packageRoot, changedFiles }) {
|
|
|
216
256
|
return { applied: true };
|
|
217
257
|
}
|
|
218
258
|
|
|
259
|
+
function syncJwtRbacClaims({ rootDir, changedFiles }) {
|
|
260
|
+
const authContractsPath = path.join(rootDir, 'packages', 'auth-contracts', 'src', 'index.ts');
|
|
261
|
+
const authServicePath = path.join(rootDir, 'packages', 'auth-api', 'src', 'auth.service.ts');
|
|
262
|
+
const authControllerPath = path.join(rootDir, 'packages', 'auth-api', 'src', 'auth.controller.ts');
|
|
263
|
+
const readmePath = path.join(rootDir, 'README.md');
|
|
264
|
+
|
|
265
|
+
if (!fs.existsSync(authContractsPath) || !fs.existsSync(authServicePath) || !fs.existsSync(authControllerPath)) {
|
|
266
|
+
return { applied: false, reason: 'auth package files are missing' };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let touched = false;
|
|
270
|
+
|
|
271
|
+
let authContracts = fs.readFileSync(authContractsPath, 'utf8').replace(/\r\n/g, '\n');
|
|
272
|
+
const originalAuthContracts = authContracts;
|
|
273
|
+
if (!authContracts.includes('permissions?: string[];')) {
|
|
274
|
+
authContracts = authContracts.replace(
|
|
275
|
+
' roles: string[];',
|
|
276
|
+
` roles: string[];
|
|
277
|
+
permissions?: string[];`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (authContracts !== originalAuthContracts) {
|
|
281
|
+
fs.writeFileSync(authContractsPath, `${authContracts.trimEnd()}\n`, 'utf8');
|
|
282
|
+
changedFiles.add(authContractsPath);
|
|
283
|
+
touched = true;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let authService = fs.readFileSync(authServicePath, 'utf8').replace(/\r\n/g, '\n');
|
|
287
|
+
const originalAuthService = authService;
|
|
288
|
+
authService = authService.replace(
|
|
289
|
+
/roles: \['user'\],/g,
|
|
290
|
+
`roles: ['admin'],
|
|
291
|
+
permissions: ['health.rbac'],`,
|
|
292
|
+
);
|
|
293
|
+
if (!authService.includes('permissions: user.permissions,')) {
|
|
294
|
+
authService = authService.replace(
|
|
295
|
+
' roles: user.roles,',
|
|
296
|
+
` roles: user.roles,
|
|
297
|
+
permissions: user.permissions,`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (!authService.includes('permissions: Array.isArray(payload.permissions) ? payload.permissions : [],')) {
|
|
301
|
+
authService = authService.replace(
|
|
302
|
+
" roles: Array.isArray(payload.roles) ? payload.roles : ['user'],",
|
|
303
|
+
` roles: Array.isArray(payload.roles) ? payload.roles : ['user'],
|
|
304
|
+
permissions: Array.isArray(payload.permissions) ? payload.permissions : [],`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (!authService.includes('demoPermissions: [')) {
|
|
308
|
+
authService = authService.replace(
|
|
309
|
+
" demoEmail: this.configService.demoEmail,",
|
|
310
|
+
` demoEmail: this.configService.demoEmail,
|
|
311
|
+
demoPermissions: ['health.rbac'],`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (authService !== originalAuthService) {
|
|
315
|
+
fs.writeFileSync(authServicePath, `${authService.trimEnd()}\n`, 'utf8');
|
|
316
|
+
changedFiles.add(authServicePath);
|
|
317
|
+
touched = true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let authController = fs.readFileSync(authControllerPath, 'utf8').replace(/\r\n/g, '\n');
|
|
321
|
+
const originalAuthController = authController;
|
|
322
|
+
if (!authController.includes('permissions: Array.isArray(payload.permissions) ? payload.permissions : [],')) {
|
|
323
|
+
authController = authController.replace(
|
|
324
|
+
" roles: Array.isArray(payload.roles) ? payload.roles : ['user'],",
|
|
325
|
+
` roles: Array.isArray(payload.roles) ? payload.roles : ['user'],
|
|
326
|
+
permissions: Array.isArray(payload.permissions) ? payload.permissions : [],`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (authController !== originalAuthController) {
|
|
330
|
+
fs.writeFileSync(authControllerPath, `${authController.trimEnd()}\n`, 'utf8');
|
|
331
|
+
changedFiles.add(authControllerPath);
|
|
332
|
+
touched = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (fs.existsSync(readmePath)) {
|
|
336
|
+
let readme = fs.readFileSync(readmePath, 'utf8').replace(/\r\n/g, '\n');
|
|
337
|
+
const originalReadme = readme;
|
|
338
|
+
if (!readme.includes('- RBAC integration: demo auth tokens include `health.rbac` permission')) {
|
|
339
|
+
const marker = 'Default demo credentials:';
|
|
340
|
+
if (readme.includes(marker)) {
|
|
341
|
+
readme = readme.replace(
|
|
342
|
+
marker,
|
|
343
|
+
`- RBAC integration: demo auth tokens include \`health.rbac\` permission
|
|
344
|
+
|
|
345
|
+
Default demo credentials:`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (readme !== originalReadme) {
|
|
350
|
+
fs.writeFileSync(readmePath, `${readme.trimEnd()}\n`, 'utf8');
|
|
351
|
+
changedFiles.add(readmePath);
|
|
352
|
+
touched = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!touched) {
|
|
357
|
+
return { applied: false, reason: 'already synced' };
|
|
358
|
+
}
|
|
359
|
+
return { applied: true };
|
|
360
|
+
}
|
|
361
|
+
|
|
219
362
|
export function syncIntegrations({ targetRoot, packageRoot, groupIds = null }) {
|
|
220
363
|
const rootDir = path.resolve(targetRoot);
|
|
221
364
|
const changedFiles = new Set();
|
package/src/run-add-module.mjs
CHANGED
|
@@ -94,7 +94,7 @@ function ensureSyncTooling({ packageRoot, targetRoot }) {
|
|
|
94
94
|
);
|
|
95
95
|
const targetScript = path.join(targetRoot, 'scripts', 'forgeon-sync-integrations.mjs');
|
|
96
96
|
|
|
97
|
-
if (fs.existsSync(sourceScript)
|
|
97
|
+
if (fs.existsSync(sourceScript)) {
|
|
98
98
|
fs.mkdirSync(path.dirname(targetScript), { recursive: true });
|
|
99
99
|
fs.copyFileSync(sourceScript, targetScript);
|
|
100
100
|
}
|
package/templates/base/README.md
CHANGED
|
@@ -37,6 +37,7 @@ pnpm forgeon:sync-integrations
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Current sync coverage:
|
|
40
|
+
- `jwt-auth + rbac`: extends demo auth tokens with the `health.rbac` permission.
|
|
40
41
|
- `jwt-auth + db-prisma`: wires persistent refresh-token storage for auth.
|
|
41
42
|
|
|
42
43
|
`create-forgeon add <module>` scans for relevant integration groups and can apply them immediately.
|
|
@@ -53,6 +53,9 @@ function detectModules(rootDir) {
|
|
|
53
53
|
jwtAuth:
|
|
54
54
|
fs.existsSync(path.join(rootDir, 'packages', 'auth-api', 'package.json')) ||
|
|
55
55
|
appModuleText.includes("from '@forgeon/auth-api'"),
|
|
56
|
+
rbac:
|
|
57
|
+
fs.existsSync(path.join(rootDir, 'packages', 'rbac', 'package.json')) ||
|
|
58
|
+
appModuleText.includes("from '@forgeon/rbac'"),
|
|
56
59
|
dbPrisma:
|
|
57
60
|
fs.existsSync(path.join(rootDir, 'packages', 'db-prisma', 'package.json')) ||
|
|
58
61
|
appModuleText.includes("from '@forgeon/db-prisma'"),
|
|
@@ -179,6 +182,109 @@ function syncJwtDbPrisma({ rootDir, changedFiles }) {
|
|
|
179
182
|
return { applied: true };
|
|
180
183
|
}
|
|
181
184
|
|
|
185
|
+
function syncJwtRbacClaims({ rootDir, changedFiles }) {
|
|
186
|
+
const authContractsPath = path.join(rootDir, 'packages', 'auth-contracts', 'src', 'index.ts');
|
|
187
|
+
const authServicePath = path.join(rootDir, 'packages', 'auth-api', 'src', 'auth.service.ts');
|
|
188
|
+
const authControllerPath = path.join(rootDir, 'packages', 'auth-api', 'src', 'auth.controller.ts');
|
|
189
|
+
const readmePath = path.join(rootDir, 'README.md');
|
|
190
|
+
|
|
191
|
+
if (!fs.existsSync(authContractsPath) || !fs.existsSync(authServicePath) || !fs.existsSync(authControllerPath)) {
|
|
192
|
+
return { applied: false, reason: 'auth package files are missing' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let touched = false;
|
|
196
|
+
|
|
197
|
+
let authContracts = fs.readFileSync(authContractsPath, 'utf8').replace(/\r\n/g, '\n');
|
|
198
|
+
const originalAuthContracts = authContracts;
|
|
199
|
+
if (!authContracts.includes('permissions?: string[];')) {
|
|
200
|
+
authContracts = authContracts.replace(
|
|
201
|
+
' roles: string[];',
|
|
202
|
+
` roles: string[];
|
|
203
|
+
permissions?: string[];`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (authContracts !== originalAuthContracts) {
|
|
207
|
+
fs.writeFileSync(authContractsPath, `${authContracts.trimEnd()}\n`, 'utf8');
|
|
208
|
+
changedFiles.add(authContractsPath);
|
|
209
|
+
touched = true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let authService = fs.readFileSync(authServicePath, 'utf8').replace(/\r\n/g, '\n');
|
|
213
|
+
const originalAuthService = authService;
|
|
214
|
+
authService = authService.replace(
|
|
215
|
+
/roles: \['user'\],/g,
|
|
216
|
+
`roles: ['admin'],
|
|
217
|
+
permissions: ['health.rbac'],`,
|
|
218
|
+
);
|
|
219
|
+
if (!authService.includes('permissions: user.permissions,')) {
|
|
220
|
+
authService = authService.replace(
|
|
221
|
+
' roles: user.roles,',
|
|
222
|
+
` roles: user.roles,
|
|
223
|
+
permissions: user.permissions,`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (!authService.includes('permissions: Array.isArray(payload.permissions) ? payload.permissions : [],')) {
|
|
227
|
+
authService = authService.replace(
|
|
228
|
+
" roles: Array.isArray(payload.roles) ? payload.roles : ['user'],",
|
|
229
|
+
` roles: Array.isArray(payload.roles) ? payload.roles : ['user'],
|
|
230
|
+
permissions: Array.isArray(payload.permissions) ? payload.permissions : [],`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (!authService.includes('demoPermissions: [')) {
|
|
234
|
+
authService = authService.replace(
|
|
235
|
+
" demoEmail: this.configService.demoEmail,",
|
|
236
|
+
` demoEmail: this.configService.demoEmail,
|
|
237
|
+
demoPermissions: ['health.rbac'],`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (authService !== originalAuthService) {
|
|
241
|
+
fs.writeFileSync(authServicePath, `${authService.trimEnd()}\n`, 'utf8');
|
|
242
|
+
changedFiles.add(authServicePath);
|
|
243
|
+
touched = true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let authController = fs.readFileSync(authControllerPath, 'utf8').replace(/\r\n/g, '\n');
|
|
247
|
+
const originalAuthController = authController;
|
|
248
|
+
if (!authController.includes('permissions: Array.isArray(payload.permissions) ? payload.permissions : [],')) {
|
|
249
|
+
authController = authController.replace(
|
|
250
|
+
" roles: Array.isArray(payload.roles) ? payload.roles : ['user'],",
|
|
251
|
+
` roles: Array.isArray(payload.roles) ? payload.roles : ['user'],
|
|
252
|
+
permissions: Array.isArray(payload.permissions) ? payload.permissions : [],`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (authController !== originalAuthController) {
|
|
256
|
+
fs.writeFileSync(authControllerPath, `${authController.trimEnd()}\n`, 'utf8');
|
|
257
|
+
changedFiles.add(authControllerPath);
|
|
258
|
+
touched = true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (fs.existsSync(readmePath)) {
|
|
262
|
+
let readme = fs.readFileSync(readmePath, 'utf8').replace(/\r\n/g, '\n');
|
|
263
|
+
const originalReadme = readme;
|
|
264
|
+
if (!readme.includes('- RBAC integration: demo auth tokens include `health.rbac` permission')) {
|
|
265
|
+
const marker = 'Default demo credentials:';
|
|
266
|
+
if (readme.includes(marker)) {
|
|
267
|
+
readme = readme.replace(
|
|
268
|
+
marker,
|
|
269
|
+
`- RBAC integration: demo auth tokens include \`health.rbac\` permission
|
|
270
|
+
|
|
271
|
+
Default demo credentials:`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (readme !== originalReadme) {
|
|
276
|
+
fs.writeFileSync(readmePath, `${readme.trimEnd()}\n`, 'utf8');
|
|
277
|
+
changedFiles.add(readmePath);
|
|
278
|
+
touched = true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!touched) {
|
|
283
|
+
return { applied: false, reason: 'already synced' };
|
|
284
|
+
}
|
|
285
|
+
return { applied: true };
|
|
286
|
+
}
|
|
287
|
+
|
|
182
288
|
function run() {
|
|
183
289
|
const rootDir = process.cwd();
|
|
184
290
|
const changedFiles = new Set();
|
|
@@ -197,6 +303,18 @@ function run() {
|
|
|
197
303
|
});
|
|
198
304
|
}
|
|
199
305
|
|
|
306
|
+
if (detected.jwtAuth && detected.rbac) {
|
|
307
|
+
summary.push({
|
|
308
|
+
feature: 'jwt-auth + rbac',
|
|
309
|
+
result: syncJwtRbacClaims({ rootDir, changedFiles }),
|
|
310
|
+
});
|
|
311
|
+
} else {
|
|
312
|
+
summary.push({
|
|
313
|
+
feature: 'jwt-auth + rbac',
|
|
314
|
+
result: { applied: false, reason: 'required modules are not both installed' },
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
200
318
|
console.log('[forgeon:sync-integrations] done');
|
|
201
319
|
for (const item of summary) {
|
|
202
320
|
if (item.result.applied) {
|