create-forgeon 0.3.20 → 0.3.21
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 +1 -1
- package/src/modules/executor.test.mjs +550 -550
- package/src/modules/files-access.mjs +375 -375
- package/src/modules/files-image.mjs +512 -512
- package/src/modules/files-quotas.mjs +365 -365
- package/src/run-add-module.test.mjs +228 -228
- package/templates/module-presets/files-quotas/packages/files-quotas/package.json +20 -20
- package/templates/module-presets/files-quotas/packages/files-quotas/src/files-quotas.service.ts +118 -118
- package/templates/module-presets/files-quotas/packages/files-quotas/src/forgeon-files-quotas.module.ts +18 -18
|
@@ -234,239 +234,239 @@ function assertRbacWiring(projectRoot) {
|
|
|
234
234
|
assert.match(readme, /accounts.*optional/i);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
function assertFilesWiring(projectRoot, expectedStorageDriver = 'local') {
|
|
238
|
-
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
239
|
-
assert.match(appModule, /filesConfig/);
|
|
240
|
-
assert.match(appModule, /filesEnvSchema/);
|
|
241
|
-
assert.match(appModule, /ForgeonFilesModule\.register\(\{/);
|
|
242
|
-
assert.doesNotMatch(appModule, /ForgeonFilesDbPrismaModule/);
|
|
243
|
-
if (expectedStorageDriver === 's3') {
|
|
244
|
-
assert.match(appModule, /ForgeonFilesS3StorageModule/);
|
|
245
|
-
assert.doesNotMatch(appModule, /ForgeonFilesLocalStorageModule/);
|
|
246
|
-
} else {
|
|
247
|
-
assert.match(appModule, /ForgeonFilesLocalStorageModule/);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
251
|
-
assert.match(apiPackage, /@forgeon\/files/);
|
|
252
|
-
assert.match(apiPackage, /pnpm --filter @forgeon\/files build/);
|
|
253
|
-
|
|
254
|
-
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
255
|
-
assert.match(apiDockerfile, /COPY packages\/files\/package\.json packages\/files\/package\.json/);
|
|
256
|
-
assert.match(apiDockerfile, /COPY packages\/files packages\/files/);
|
|
257
|
-
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files build/);
|
|
258
|
-
|
|
259
|
-
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
260
|
-
assert.match(apiEnv, /FILES_ENABLED=true/);
|
|
261
|
-
assert.match(apiEnv, new RegExp('FILES_STORAGE_DRIVER=' + expectedStorageDriver));
|
|
262
|
-
assert.match(apiEnv, /FILES_PUBLIC_BASE_PATH=\/files/);
|
|
263
|
-
assert.match(apiEnv, /FILES_MAX_FILE_SIZE_BYTES=10485760/);
|
|
264
|
-
assert.match(apiEnv, /FILES_ALLOWED_MIME_PREFIXES=image\/,application\/pdf,text\//);
|
|
265
|
-
|
|
266
|
-
const healthController = fs.readFileSync(
|
|
267
|
-
path.join(projectRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts'),
|
|
268
|
-
'utf8',
|
|
269
|
-
);
|
|
270
|
-
assert.match(healthController, /@Post\('files'\)/);
|
|
271
|
-
assert.match(healthController, /@Get\('files-variants'\)/);
|
|
272
|
-
assert.match(healthController, /filesService\.createProbeRecord/);
|
|
273
|
-
assert.match(healthController, /filesService\.getVariantsProbeStatus/);
|
|
274
|
-
assert.match(healthController, /filesService\.deleteByPublicId/);
|
|
275
|
-
|
|
276
|
-
const filesController = fs.readFileSync(
|
|
277
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'files.controller.ts'),
|
|
278
|
-
'utf8',
|
|
279
|
-
);
|
|
280
|
-
assert.match(filesController, /@Query\('variant'\) variantQuery\?: string/);
|
|
281
|
-
assert.match(filesController, /parseVariant\(variantQuery\)/);
|
|
282
|
-
assert.match(filesController, /@Delete\(':publicId'\)/);
|
|
283
|
-
|
|
284
|
-
const filesService = fs.readFileSync(
|
|
285
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
|
|
286
|
-
'utf8',
|
|
287
|
-
);
|
|
288
|
-
assert.match(filesService, /FilesStore/);
|
|
289
|
-
assert.match(filesService, /FILES_STORAGE_ADAPTER/);
|
|
290
|
-
assert.match(filesService, /requireStorageAdapter/);
|
|
291
|
-
assert.match(filesService, /getOrCreateBlob/);
|
|
292
|
-
assert.match(filesService, /cleanupReferencedBlobs/);
|
|
293
|
-
assert.match(filesService, /isUniqueConstraintError/);
|
|
294
|
-
assert.match(filesService, /storageAdapter\.put/);
|
|
295
|
-
assert.match(filesService, /filesStore\.createBlob/);
|
|
296
|
-
assert.match(filesService, /filesStore\.deleteBlobIfUnreferenced/);
|
|
297
|
-
assert.doesNotMatch(filesService, /FILES_PERSISTENCE_PORT/);
|
|
298
|
-
assert.doesNotMatch(filesService, /PrismaService/);
|
|
299
|
-
assert.doesNotMatch(filesService, /@aws-sdk\/client-s3/);
|
|
300
|
-
|
|
301
|
-
const filesPorts = fs.readFileSync(
|
|
302
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'files.ports.ts'),
|
|
303
|
-
'utf8',
|
|
304
|
-
);
|
|
305
|
-
assert.doesNotMatch(filesPorts, /FILES_PERSISTENCE_PORT/);
|
|
306
|
-
assert.doesNotMatch(filesPorts, /interface FilesPersistencePort/);
|
|
307
|
-
assert.match(filesPorts, /FILES_STORAGE_ADAPTER/);
|
|
308
|
-
assert.match(filesPorts, /interface FilesStorageAdapter/);
|
|
309
|
-
|
|
310
|
-
const filesStore = fs.readFileSync(
|
|
311
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'files.store.ts'),
|
|
312
|
-
'utf8',
|
|
313
|
-
);
|
|
314
|
-
assert.match(filesStore, /PrismaService/);
|
|
315
|
-
assert.match(filesStore, /fileBlob\.deleteMany/);
|
|
316
|
-
|
|
317
|
-
const filesModule = fs.readFileSync(
|
|
318
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'forgeon-files.module.ts'),
|
|
319
|
-
'utf8',
|
|
320
|
-
);
|
|
321
|
-
assert.match(filesModule, /ForgeonFilesModuleOptions/);
|
|
322
|
-
assert.match(filesModule, /static register\(options: ForgeonFilesModuleOptions = \{\}\)/);
|
|
323
|
-
assert.match(filesModule, /DbPrismaModule/);
|
|
324
|
-
assert.match(filesModule, /FilesStore/);
|
|
325
|
-
assert.doesNotMatch(filesModule, /FILES_PERSISTENCE_PORT/);
|
|
326
|
-
|
|
327
|
-
const filesPackage = fs.readFileSync(path.join(projectRoot, 'packages', 'files', 'package.json'), 'utf8');
|
|
328
|
-
assert.match(filesPackage, /@forgeon\/db-prisma/);
|
|
329
|
-
|
|
330
|
-
const prismaFilesDir = path.join(projectRoot, 'apps', 'api', 'src', 'files');
|
|
331
|
-
assert.equal(fs.existsSync(path.join(prismaFilesDir, 'prisma-files-persistence.store.ts')), false);
|
|
332
|
-
assert.equal(fs.existsSync(path.join(prismaFilesDir, 'forgeon-files-db-prisma.module.ts')), false);
|
|
333
|
-
|
|
334
|
-
assertWebProbeShell(projectRoot);
|
|
335
|
-
const probesTs = readWebProbes(projectRoot);
|
|
336
|
-
assert.match(probesTs, /"id": "files"/);
|
|
337
|
-
assert.match(probesTs, /"buttonLabel": "Check files probe \(create metadata\)"/);
|
|
338
|
-
assert.match(probesTs, /"resultTitle": "Files probe response"/);
|
|
339
|
-
assert.match(probesTs, /"id": "files-variants"/);
|
|
340
|
-
assert.match(probesTs, /"buttonLabel": "Check files variants capability"/);
|
|
341
|
-
assert.match(probesTs, /"resultTitle": "Files variants probe response"/);
|
|
342
|
-
|
|
343
|
-
const schema = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'prisma', 'schema.prisma'), 'utf8');
|
|
344
|
-
assert.match(schema, /model FileRecord \{/);
|
|
345
|
-
assert.match(schema, /variants\s+FileVariant\[\]/);
|
|
346
|
-
assert.match(schema, /model FileVariant \{/);
|
|
347
|
-
assert.match(schema, /model FileBlob \{/);
|
|
348
|
-
assert.match(schema, /blobId\s+String/);
|
|
349
|
-
assert.match(schema, /@@unique\(\[hash,\s*size,\s*mimeType,\s*storageDriver\]\)/);
|
|
350
|
-
assert.match(schema, /@@unique\(\[fileId,\s*variantKey\]\)/);
|
|
351
|
-
assert.match(schema, /publicId\s+String\s+@unique/);
|
|
352
|
-
assert.match(schema, /@@index\(\[ownerType,\s*ownerId,\s*createdAt\]\)/);
|
|
353
|
-
|
|
354
|
-
const migration = path.join(
|
|
355
|
-
projectRoot,
|
|
356
|
-
'apps',
|
|
357
|
-
'api',
|
|
358
|
-
'prisma',
|
|
359
|
-
'migrations',
|
|
360
|
-
'20260306_files_file_record',
|
|
361
|
-
'migration.sql',
|
|
362
|
-
);
|
|
363
|
-
assert.equal(fs.existsSync(migration), true);
|
|
364
|
-
|
|
365
|
-
const variantMigration = path.join(
|
|
366
|
-
projectRoot,
|
|
367
|
-
'apps',
|
|
368
|
-
'api',
|
|
369
|
-
'prisma',
|
|
370
|
-
'migrations',
|
|
371
|
-
'20260306_files_file_variant',
|
|
372
|
-
'migration.sql',
|
|
373
|
-
);
|
|
374
|
-
assert.equal(fs.existsSync(variantMigration), true);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function assertFilesLocalWiring(projectRoot) {
|
|
378
|
-
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
379
|
-
assert.match(appModule, /filesLocalConfig/);
|
|
380
|
-
assert.match(appModule, /filesLocalEnvSchemaZod/);
|
|
381
|
-
assert.match(appModule, /FilesLocalConfigModule/);
|
|
382
|
-
assert.match(appModule, /ForgeonFilesLocalStorageModule/);
|
|
383
|
-
|
|
384
|
-
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
385
|
-
assert.match(apiPackage, /@forgeon\/files-local/);
|
|
386
|
-
assert.match(apiPackage, /pnpm --filter @forgeon\/files-local build/);
|
|
387
|
-
|
|
388
|
-
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
389
|
-
assert.match(apiDockerfile, /COPY packages\/files-local\/package\.json packages\/files-local\/package\.json/);
|
|
390
|
-
assert.match(apiDockerfile, /COPY packages\/files-local packages\/files-local/);
|
|
391
|
-
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-local build/);
|
|
392
|
-
|
|
393
|
-
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
394
|
-
assert.match(apiEnv, /FILES_LOCAL_ROOT=storage\/uploads/);
|
|
395
|
-
|
|
396
|
-
const localModule = fs.readFileSync(
|
|
397
|
-
path.join(projectRoot, 'packages', 'files-local', 'src', 'forgeon-files-local-storage.module.ts'),
|
|
398
|
-
'utf8',
|
|
399
|
-
);
|
|
400
|
-
assert.match(localModule, /ForgeonFilesLocalStorageModule/);
|
|
401
|
-
assert.match(localModule, /FORGEON_FILES_STORAGE_ADAPTER/);
|
|
402
|
-
|
|
403
|
-
const localAdapter = fs.readFileSync(
|
|
404
|
-
path.join(projectRoot, 'packages', 'files-local', 'src', 'local-files-storage.adapter.ts'),
|
|
405
|
-
'utf8',
|
|
406
|
-
);
|
|
407
|
-
assert.match(localAdapter, /readonly driver = 'local'/);
|
|
408
|
-
assert.match(localAdapter, /createReadStream/);
|
|
409
|
-
assert.match(localAdapter, /writeFile/);
|
|
410
|
-
|
|
411
|
-
const gitignore = fs.readFileSync(path.join(projectRoot, '.gitignore'), 'utf8');
|
|
412
|
-
assert.match(gitignore, /storage\//);
|
|
413
|
-
|
|
414
|
-
const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
|
|
415
|
-
assert.match(compose, /files_data:\/app\/storage/);
|
|
416
|
-
assert.match(compose, /^\s{2}files_data:\s*$/m);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function assertFilesS3Wiring(projectRoot) {
|
|
420
|
-
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
421
|
-
assert.match(appModule, /filesS3Config/);
|
|
422
|
-
assert.match(appModule, /filesS3EnvSchemaZod/);
|
|
423
|
-
assert.match(appModule, /FilesS3ConfigModule/);
|
|
424
|
-
assert.match(appModule, /ForgeonFilesS3StorageModule/);
|
|
425
|
-
|
|
426
|
-
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
427
|
-
assert.match(apiPackage, /@forgeon\/files-s3/);
|
|
428
|
-
assert.match(apiPackage, /pnpm --filter @forgeon\/files-s3 build/);
|
|
429
|
-
|
|
430
|
-
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
431
|
-
assert.match(apiDockerfile, /COPY packages\/files-s3\/package\.json packages\/files-s3\/package\.json/);
|
|
432
|
-
assert.match(apiDockerfile, /COPY packages\/files-s3 packages\/files-s3/);
|
|
433
|
-
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-s3 build/);
|
|
434
|
-
|
|
435
|
-
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
436
|
-
assert.match(apiEnv, /FILES_STORAGE_DRIVER=s3/);
|
|
437
|
-
assert.match(apiEnv, /FILES_S3_PROVIDER_PRESET=minio/);
|
|
438
|
-
assert.match(apiEnv, /FILES_S3_BUCKET=forgeon-files/);
|
|
439
|
-
assert.match(apiEnv, /FILES_S3_REGION=/);
|
|
440
|
-
assert.match(apiEnv, /FILES_S3_ENDPOINT=/);
|
|
441
|
-
assert.match(apiEnv, /FILES_S3_FORCE_PATH_STYLE=/);
|
|
442
|
-
assert.match(apiEnv, /FILES_S3_MAX_ATTEMPTS=3/);
|
|
443
|
-
|
|
444
|
-
const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
|
|
445
|
-
assert.match(compose, /FILES_S3_PROVIDER_PRESET: \$\{FILES_S3_PROVIDER_PRESET\}/);
|
|
446
|
-
assert.match(compose, /FILES_S3_MAX_ATTEMPTS: \$\{FILES_S3_MAX_ATTEMPTS\}/);
|
|
447
|
-
|
|
448
|
-
const filesS3Package = fs.readFileSync(
|
|
449
|
-
path.join(projectRoot, 'packages', 'files-s3', 'package.json'),
|
|
450
|
-
'utf8',
|
|
451
|
-
);
|
|
452
|
-
assert.match(filesS3Package, /@aws-sdk\/client-s3/);
|
|
453
|
-
|
|
454
|
-
const s3Module = fs.readFileSync(
|
|
455
|
-
path.join(projectRoot, 'packages', 'files-s3', 'src', 'forgeon-files-s3-storage.module.ts'),
|
|
456
|
-
'utf8',
|
|
457
|
-
);
|
|
458
|
-
assert.match(s3Module, /ForgeonFilesS3StorageModule/);
|
|
459
|
-
assert.match(s3Module, /FORGEON_FILES_STORAGE_ADAPTER/);
|
|
460
|
-
|
|
461
|
-
const s3Adapter = fs.readFileSync(
|
|
462
|
-
path.join(projectRoot, 'packages', 'files-s3', 'src', 's3-files-storage.adapter.ts'),
|
|
463
|
-
'utf8',
|
|
464
|
-
);
|
|
465
|
-
assert.match(s3Adapter, /readonly driver = 's3'/);
|
|
466
|
-
assert.match(s3Adapter, /@aws-sdk\/client-s3/);
|
|
467
|
-
assert.match(s3Adapter, /loadS3Module/);
|
|
468
|
-
}
|
|
469
|
-
|
|
237
|
+
function assertFilesWiring(projectRoot, expectedStorageDriver = 'local') {
|
|
238
|
+
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
239
|
+
assert.match(appModule, /filesConfig/);
|
|
240
|
+
assert.match(appModule, /filesEnvSchema/);
|
|
241
|
+
assert.match(appModule, /ForgeonFilesModule\.register\(\{/);
|
|
242
|
+
assert.doesNotMatch(appModule, /ForgeonFilesDbPrismaModule/);
|
|
243
|
+
if (expectedStorageDriver === 's3') {
|
|
244
|
+
assert.match(appModule, /ForgeonFilesS3StorageModule/);
|
|
245
|
+
assert.doesNotMatch(appModule, /ForgeonFilesLocalStorageModule/);
|
|
246
|
+
} else {
|
|
247
|
+
assert.match(appModule, /ForgeonFilesLocalStorageModule/);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
251
|
+
assert.match(apiPackage, /@forgeon\/files/);
|
|
252
|
+
assert.match(apiPackage, /pnpm --filter @forgeon\/files build/);
|
|
253
|
+
|
|
254
|
+
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
255
|
+
assert.match(apiDockerfile, /COPY packages\/files\/package\.json packages\/files\/package\.json/);
|
|
256
|
+
assert.match(apiDockerfile, /COPY packages\/files packages\/files/);
|
|
257
|
+
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files build/);
|
|
258
|
+
|
|
259
|
+
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
260
|
+
assert.match(apiEnv, /FILES_ENABLED=true/);
|
|
261
|
+
assert.match(apiEnv, new RegExp('FILES_STORAGE_DRIVER=' + expectedStorageDriver));
|
|
262
|
+
assert.match(apiEnv, /FILES_PUBLIC_BASE_PATH=\/files/);
|
|
263
|
+
assert.match(apiEnv, /FILES_MAX_FILE_SIZE_BYTES=10485760/);
|
|
264
|
+
assert.match(apiEnv, /FILES_ALLOWED_MIME_PREFIXES=image\/,application\/pdf,text\//);
|
|
265
|
+
|
|
266
|
+
const healthController = fs.readFileSync(
|
|
267
|
+
path.join(projectRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts'),
|
|
268
|
+
'utf8',
|
|
269
|
+
);
|
|
270
|
+
assert.match(healthController, /@Post\('files'\)/);
|
|
271
|
+
assert.match(healthController, /@Get\('files-variants'\)/);
|
|
272
|
+
assert.match(healthController, /filesService\.createProbeRecord/);
|
|
273
|
+
assert.match(healthController, /filesService\.getVariantsProbeStatus/);
|
|
274
|
+
assert.match(healthController, /filesService\.deleteByPublicId/);
|
|
275
|
+
|
|
276
|
+
const filesController = fs.readFileSync(
|
|
277
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'files.controller.ts'),
|
|
278
|
+
'utf8',
|
|
279
|
+
);
|
|
280
|
+
assert.match(filesController, /@Query\('variant'\) variantQuery\?: string/);
|
|
281
|
+
assert.match(filesController, /parseVariant\(variantQuery\)/);
|
|
282
|
+
assert.match(filesController, /@Delete\(':publicId'\)/);
|
|
283
|
+
|
|
284
|
+
const filesService = fs.readFileSync(
|
|
285
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
|
|
286
|
+
'utf8',
|
|
287
|
+
);
|
|
288
|
+
assert.match(filesService, /FilesStore/);
|
|
289
|
+
assert.match(filesService, /FILES_STORAGE_ADAPTER/);
|
|
290
|
+
assert.match(filesService, /requireStorageAdapter/);
|
|
291
|
+
assert.match(filesService, /getOrCreateBlob/);
|
|
292
|
+
assert.match(filesService, /cleanupReferencedBlobs/);
|
|
293
|
+
assert.match(filesService, /isUniqueConstraintError/);
|
|
294
|
+
assert.match(filesService, /storageAdapter\.put/);
|
|
295
|
+
assert.match(filesService, /filesStore\.createBlob/);
|
|
296
|
+
assert.match(filesService, /filesStore\.deleteBlobIfUnreferenced/);
|
|
297
|
+
assert.doesNotMatch(filesService, /FILES_PERSISTENCE_PORT/);
|
|
298
|
+
assert.doesNotMatch(filesService, /PrismaService/);
|
|
299
|
+
assert.doesNotMatch(filesService, /@aws-sdk\/client-s3/);
|
|
300
|
+
|
|
301
|
+
const filesPorts = fs.readFileSync(
|
|
302
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'files.ports.ts'),
|
|
303
|
+
'utf8',
|
|
304
|
+
);
|
|
305
|
+
assert.doesNotMatch(filesPorts, /FILES_PERSISTENCE_PORT/);
|
|
306
|
+
assert.doesNotMatch(filesPorts, /interface FilesPersistencePort/);
|
|
307
|
+
assert.match(filesPorts, /FILES_STORAGE_ADAPTER/);
|
|
308
|
+
assert.match(filesPorts, /interface FilesStorageAdapter/);
|
|
309
|
+
|
|
310
|
+
const filesStore = fs.readFileSync(
|
|
311
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'files.store.ts'),
|
|
312
|
+
'utf8',
|
|
313
|
+
);
|
|
314
|
+
assert.match(filesStore, /PrismaService/);
|
|
315
|
+
assert.match(filesStore, /fileBlob\.deleteMany/);
|
|
316
|
+
|
|
317
|
+
const filesModule = fs.readFileSync(
|
|
318
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'forgeon-files.module.ts'),
|
|
319
|
+
'utf8',
|
|
320
|
+
);
|
|
321
|
+
assert.match(filesModule, /ForgeonFilesModuleOptions/);
|
|
322
|
+
assert.match(filesModule, /static register\(options: ForgeonFilesModuleOptions = \{\}\)/);
|
|
323
|
+
assert.match(filesModule, /DbPrismaModule/);
|
|
324
|
+
assert.match(filesModule, /FilesStore/);
|
|
325
|
+
assert.doesNotMatch(filesModule, /FILES_PERSISTENCE_PORT/);
|
|
326
|
+
|
|
327
|
+
const filesPackage = fs.readFileSync(path.join(projectRoot, 'packages', 'files', 'package.json'), 'utf8');
|
|
328
|
+
assert.match(filesPackage, /@forgeon\/db-prisma/);
|
|
329
|
+
|
|
330
|
+
const prismaFilesDir = path.join(projectRoot, 'apps', 'api', 'src', 'files');
|
|
331
|
+
assert.equal(fs.existsSync(path.join(prismaFilesDir, 'prisma-files-persistence.store.ts')), false);
|
|
332
|
+
assert.equal(fs.existsSync(path.join(prismaFilesDir, 'forgeon-files-db-prisma.module.ts')), false);
|
|
333
|
+
|
|
334
|
+
assertWebProbeShell(projectRoot);
|
|
335
|
+
const probesTs = readWebProbes(projectRoot);
|
|
336
|
+
assert.match(probesTs, /"id": "files"/);
|
|
337
|
+
assert.match(probesTs, /"buttonLabel": "Check files probe \(create metadata\)"/);
|
|
338
|
+
assert.match(probesTs, /"resultTitle": "Files probe response"/);
|
|
339
|
+
assert.match(probesTs, /"id": "files-variants"/);
|
|
340
|
+
assert.match(probesTs, /"buttonLabel": "Check files variants capability"/);
|
|
341
|
+
assert.match(probesTs, /"resultTitle": "Files variants probe response"/);
|
|
342
|
+
|
|
343
|
+
const schema = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'prisma', 'schema.prisma'), 'utf8');
|
|
344
|
+
assert.match(schema, /model FileRecord \{/);
|
|
345
|
+
assert.match(schema, /variants\s+FileVariant\[\]/);
|
|
346
|
+
assert.match(schema, /model FileVariant \{/);
|
|
347
|
+
assert.match(schema, /model FileBlob \{/);
|
|
348
|
+
assert.match(schema, /blobId\s+String/);
|
|
349
|
+
assert.match(schema, /@@unique\(\[hash,\s*size,\s*mimeType,\s*storageDriver\]\)/);
|
|
350
|
+
assert.match(schema, /@@unique\(\[fileId,\s*variantKey\]\)/);
|
|
351
|
+
assert.match(schema, /publicId\s+String\s+@unique/);
|
|
352
|
+
assert.match(schema, /@@index\(\[ownerType,\s*ownerId,\s*createdAt\]\)/);
|
|
353
|
+
|
|
354
|
+
const migration = path.join(
|
|
355
|
+
projectRoot,
|
|
356
|
+
'apps',
|
|
357
|
+
'api',
|
|
358
|
+
'prisma',
|
|
359
|
+
'migrations',
|
|
360
|
+
'20260306_files_file_record',
|
|
361
|
+
'migration.sql',
|
|
362
|
+
);
|
|
363
|
+
assert.equal(fs.existsSync(migration), true);
|
|
364
|
+
|
|
365
|
+
const variantMigration = path.join(
|
|
366
|
+
projectRoot,
|
|
367
|
+
'apps',
|
|
368
|
+
'api',
|
|
369
|
+
'prisma',
|
|
370
|
+
'migrations',
|
|
371
|
+
'20260306_files_file_variant',
|
|
372
|
+
'migration.sql',
|
|
373
|
+
);
|
|
374
|
+
assert.equal(fs.existsSync(variantMigration), true);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function assertFilesLocalWiring(projectRoot) {
|
|
378
|
+
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
379
|
+
assert.match(appModule, /filesLocalConfig/);
|
|
380
|
+
assert.match(appModule, /filesLocalEnvSchemaZod/);
|
|
381
|
+
assert.match(appModule, /FilesLocalConfigModule/);
|
|
382
|
+
assert.match(appModule, /ForgeonFilesLocalStorageModule/);
|
|
383
|
+
|
|
384
|
+
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
385
|
+
assert.match(apiPackage, /@forgeon\/files-local/);
|
|
386
|
+
assert.match(apiPackage, /pnpm --filter @forgeon\/files-local build/);
|
|
387
|
+
|
|
388
|
+
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
389
|
+
assert.match(apiDockerfile, /COPY packages\/files-local\/package\.json packages\/files-local\/package\.json/);
|
|
390
|
+
assert.match(apiDockerfile, /COPY packages\/files-local packages\/files-local/);
|
|
391
|
+
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-local build/);
|
|
392
|
+
|
|
393
|
+
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
394
|
+
assert.match(apiEnv, /FILES_LOCAL_ROOT=storage\/uploads/);
|
|
395
|
+
|
|
396
|
+
const localModule = fs.readFileSync(
|
|
397
|
+
path.join(projectRoot, 'packages', 'files-local', 'src', 'forgeon-files-local-storage.module.ts'),
|
|
398
|
+
'utf8',
|
|
399
|
+
);
|
|
400
|
+
assert.match(localModule, /ForgeonFilesLocalStorageModule/);
|
|
401
|
+
assert.match(localModule, /FORGEON_FILES_STORAGE_ADAPTER/);
|
|
402
|
+
|
|
403
|
+
const localAdapter = fs.readFileSync(
|
|
404
|
+
path.join(projectRoot, 'packages', 'files-local', 'src', 'local-files-storage.adapter.ts'),
|
|
405
|
+
'utf8',
|
|
406
|
+
);
|
|
407
|
+
assert.match(localAdapter, /readonly driver = 'local'/);
|
|
408
|
+
assert.match(localAdapter, /createReadStream/);
|
|
409
|
+
assert.match(localAdapter, /writeFile/);
|
|
410
|
+
|
|
411
|
+
const gitignore = fs.readFileSync(path.join(projectRoot, '.gitignore'), 'utf8');
|
|
412
|
+
assert.match(gitignore, /storage\//);
|
|
413
|
+
|
|
414
|
+
const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
|
|
415
|
+
assert.match(compose, /files_data:\/app\/storage/);
|
|
416
|
+
assert.match(compose, /^\s{2}files_data:\s*$/m);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function assertFilesS3Wiring(projectRoot) {
|
|
420
|
+
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
421
|
+
assert.match(appModule, /filesS3Config/);
|
|
422
|
+
assert.match(appModule, /filesS3EnvSchemaZod/);
|
|
423
|
+
assert.match(appModule, /FilesS3ConfigModule/);
|
|
424
|
+
assert.match(appModule, /ForgeonFilesS3StorageModule/);
|
|
425
|
+
|
|
426
|
+
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
427
|
+
assert.match(apiPackage, /@forgeon\/files-s3/);
|
|
428
|
+
assert.match(apiPackage, /pnpm --filter @forgeon\/files-s3 build/);
|
|
429
|
+
|
|
430
|
+
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
431
|
+
assert.match(apiDockerfile, /COPY packages\/files-s3\/package\.json packages\/files-s3\/package\.json/);
|
|
432
|
+
assert.match(apiDockerfile, /COPY packages\/files-s3 packages\/files-s3/);
|
|
433
|
+
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-s3 build/);
|
|
434
|
+
|
|
435
|
+
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
436
|
+
assert.match(apiEnv, /FILES_STORAGE_DRIVER=s3/);
|
|
437
|
+
assert.match(apiEnv, /FILES_S3_PROVIDER_PRESET=minio/);
|
|
438
|
+
assert.match(apiEnv, /FILES_S3_BUCKET=forgeon-files/);
|
|
439
|
+
assert.match(apiEnv, /FILES_S3_REGION=/);
|
|
440
|
+
assert.match(apiEnv, /FILES_S3_ENDPOINT=/);
|
|
441
|
+
assert.match(apiEnv, /FILES_S3_FORCE_PATH_STYLE=/);
|
|
442
|
+
assert.match(apiEnv, /FILES_S3_MAX_ATTEMPTS=3/);
|
|
443
|
+
|
|
444
|
+
const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
|
|
445
|
+
assert.match(compose, /FILES_S3_PROVIDER_PRESET: \$\{FILES_S3_PROVIDER_PRESET\}/);
|
|
446
|
+
assert.match(compose, /FILES_S3_MAX_ATTEMPTS: \$\{FILES_S3_MAX_ATTEMPTS\}/);
|
|
447
|
+
|
|
448
|
+
const filesS3Package = fs.readFileSync(
|
|
449
|
+
path.join(projectRoot, 'packages', 'files-s3', 'package.json'),
|
|
450
|
+
'utf8',
|
|
451
|
+
);
|
|
452
|
+
assert.match(filesS3Package, /@aws-sdk\/client-s3/);
|
|
453
|
+
|
|
454
|
+
const s3Module = fs.readFileSync(
|
|
455
|
+
path.join(projectRoot, 'packages', 'files-s3', 'src', 'forgeon-files-s3-storage.module.ts'),
|
|
456
|
+
'utf8',
|
|
457
|
+
);
|
|
458
|
+
assert.match(s3Module, /ForgeonFilesS3StorageModule/);
|
|
459
|
+
assert.match(s3Module, /FORGEON_FILES_STORAGE_ADAPTER/);
|
|
460
|
+
|
|
461
|
+
const s3Adapter = fs.readFileSync(
|
|
462
|
+
path.join(projectRoot, 'packages', 'files-s3', 'src', 's3-files-storage.adapter.ts'),
|
|
463
|
+
'utf8',
|
|
464
|
+
);
|
|
465
|
+
assert.match(s3Adapter, /readonly driver = 's3'/);
|
|
466
|
+
assert.match(s3Adapter, /@aws-sdk\/client-s3/);
|
|
467
|
+
assert.match(s3Adapter, /loadS3Module/);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
470
|
function assertFilesAccessWiring(projectRoot) {
|
|
471
471
|
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
472
472
|
assert.match(appModule, /ForgeonFilesAccessModule/);
|
|
@@ -532,14 +532,14 @@ function assertFilesQuotasWiring(projectRoot) {
|
|
|
532
532
|
assert.match(appModule, /filesQuotasEnvSchema/);
|
|
533
533
|
assert.match(appModule, /ForgeonFilesQuotasModule/);
|
|
534
534
|
|
|
535
|
-
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
536
|
-
assert.match(apiPackage, /@forgeon\/files-quotas/);
|
|
537
|
-
assert.match(apiPackage, /pnpm --filter @forgeon\/files-quotas build/);
|
|
538
|
-
assert.equal(
|
|
539
|
-
apiPackage.indexOf('pnpm --filter @forgeon/files-quotas build')
|
|
540
|
-
apiPackage.indexOf('pnpm --filter @forgeon/files build'),
|
|
541
|
-
true,
|
|
542
|
-
);
|
|
535
|
+
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
536
|
+
assert.match(apiPackage, /@forgeon\/files-quotas/);
|
|
537
|
+
assert.match(apiPackage, /pnpm --filter @forgeon\/files-quotas build/);
|
|
538
|
+
assert.equal(
|
|
539
|
+
apiPackage.indexOf('pnpm --filter @forgeon/files-quotas build') >
|
|
540
|
+
apiPackage.indexOf('pnpm --filter @forgeon/files build'),
|
|
541
|
+
true,
|
|
542
|
+
);
|
|
543
543
|
|
|
544
544
|
const filesPackage = fs.readFileSync(path.join(projectRoot, 'packages', 'files', 'package.json'), 'utf8');
|
|
545
545
|
assert.doesNotMatch(filesPackage, /@forgeon\/files-quotas/);
|
|
@@ -550,13 +550,13 @@ function assertFilesQuotasWiring(projectRoot) {
|
|
|
550
550
|
apiDockerfile,
|
|
551
551
|
/COPY packages\/files-quotas\/package\.json packages\/files-quotas\/package\.json/,
|
|
552
552
|
);
|
|
553
|
-
assert.match(apiDockerfile, /COPY packages\/files-quotas packages\/files-quotas/);
|
|
554
|
-
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-quotas build/);
|
|
555
|
-
assert.equal(
|
|
556
|
-
apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files-quotas build')
|
|
557
|
-
apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files build'),
|
|
558
|
-
true,
|
|
559
|
-
);
|
|
553
|
+
assert.match(apiDockerfile, /COPY packages\/files-quotas packages\/files-quotas/);
|
|
554
|
+
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/files-quotas build/);
|
|
555
|
+
assert.equal(
|
|
556
|
+
apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files-quotas build') >
|
|
557
|
+
apiDockerfile.indexOf('RUN pnpm --filter @forgeon/files build'),
|
|
558
|
+
true,
|
|
559
|
+
);
|
|
560
560
|
|
|
561
561
|
const filesController = fs.readFileSync(
|
|
562
562
|
path.join(projectRoot, 'packages', 'files', 'src', 'files.controller.ts'),
|
|
@@ -617,19 +617,19 @@ function assertFilesImageWiring(projectRoot) {
|
|
|
617
617
|
);
|
|
618
618
|
assert.match(filesModule, /ForgeonFilesImageModule/);
|
|
619
619
|
|
|
620
|
-
const filesService = fs.readFileSync(
|
|
621
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
|
|
622
|
-
'utf8',
|
|
623
|
-
);
|
|
624
|
-
assert.match(filesService, /FilesImageService/);
|
|
625
|
-
assert.match(filesService, /filesImageService\.sanitizeForStorage/);
|
|
626
|
-
assert.match(filesService, /sanitizeForStorage\({/);
|
|
627
|
-
assert.match(filesService, /auditContext: input\.auditContext/);
|
|
628
|
-
assert.equal(
|
|
629
|
-
(filesService.match(/protected normalizeFileName\(originalName: string, extension: string, suffix\?: string\): string \{/g) ?? [])
|
|
630
|
-
.length,
|
|
631
|
-
1,
|
|
632
|
-
);
|
|
620
|
+
const filesService = fs.readFileSync(
|
|
621
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
|
|
622
|
+
'utf8',
|
|
623
|
+
);
|
|
624
|
+
assert.match(filesService, /FilesImageService/);
|
|
625
|
+
assert.match(filesService, /filesImageService\.sanitizeForStorage/);
|
|
626
|
+
assert.match(filesService, /sanitizeForStorage\({/);
|
|
627
|
+
assert.match(filesService, /auditContext: input\.auditContext/);
|
|
628
|
+
assert.equal(
|
|
629
|
+
(filesService.match(/protected normalizeFileName\(originalName: string, extension: string, suffix\?: string\): string \{/g) ?? [])
|
|
630
|
+
.length,
|
|
631
|
+
1,
|
|
632
|
+
);
|
|
633
633
|
|
|
634
634
|
const filesController = fs.readFileSync(
|
|
635
635
|
path.join(projectRoot, 'packages', 'files', 'src', 'files.controller.ts'),
|
|
@@ -709,108 +709,108 @@ function assertFilesImageWiring(projectRoot) {
|
|
|
709
709
|
assert.match(readme, /metadata is stripped before storage/i);
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
function assertAccountsWiring(projectRoot) {
|
|
713
|
-
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
714
|
-
assert.match(apiPackage, /@forgeon\/accounts-api/);
|
|
715
|
-
assert.match(apiPackage, /@forgeon\/accounts-contracts/);
|
|
716
|
-
assert.match(apiPackage, /pnpm --filter @forgeon\/accounts-contracts build/);
|
|
717
|
-
assert.match(apiPackage, /pnpm --filter @forgeon\/accounts-api build/);
|
|
718
|
-
|
|
719
|
-
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
720
|
-
assert.match(appModule, /authConfig/);
|
|
721
|
-
assert.match(appModule, /authEnvSchema/);
|
|
722
|
-
assert.match(appModule, /ForgeonAccountsModule\.register\(\{/);
|
|
723
|
-
assert.match(appModule, /UsersModule\.register\(\{\}\)/);
|
|
724
|
-
assert.doesNotMatch(appModule, /ACCOUNTS_PERSISTENCE_PORT/);
|
|
725
|
-
assert.doesNotMatch(appModule, /PrismaAccountsPersistenceStore/);
|
|
726
|
-
assert.doesNotMatch(appModule, /AUTH_REFRESH_TOKEN_STORE/);
|
|
727
|
-
|
|
728
|
-
const healthController = fs.readFileSync(
|
|
729
|
-
path.join(projectRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts'),
|
|
730
|
-
'utf8',
|
|
731
|
-
);
|
|
732
|
-
assert.match(healthController, /@Get\('auth'\)/);
|
|
733
|
-
assert.match(healthController, /authService\.getProbeStatus/);
|
|
734
|
-
assert.match(healthController, /\$queryRaw/);
|
|
735
|
-
assert.doesNotMatch(healthController, /data:\s*\{\s*email\s*\}/);
|
|
736
|
-
assert.doesNotMatch(healthController, /,\s*,/);
|
|
737
|
-
|
|
738
|
-
assertWebProbeShell(projectRoot);
|
|
739
|
-
const probesTs = readWebProbes(projectRoot);
|
|
740
|
-
assert.match(probesTs, /"id": "auth"/);
|
|
741
|
-
assert.match(probesTs, /"buttonLabel": "Check accounts probe"/);
|
|
742
|
-
assert.match(probesTs, /"resultTitle": "Accounts probe response"/);
|
|
743
|
-
|
|
744
|
-
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
745
|
-
assert.match(
|
|
746
|
-
apiDockerfile,
|
|
747
|
-
/COPY packages\/accounts-contracts\/package\.json packages\/accounts-contracts\/package\.json/,
|
|
748
|
-
);
|
|
749
|
-
assert.match(apiDockerfile, /COPY packages\/accounts-api\/package\.json packages\/accounts-api\/package\.json/);
|
|
750
|
-
assert.match(apiDockerfile, /COPY packages\/accounts-contracts packages\/accounts-contracts/);
|
|
751
|
-
assert.match(apiDockerfile, /COPY packages\/accounts-api packages\/accounts-api/);
|
|
752
|
-
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/accounts-contracts build/);
|
|
753
|
-
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/accounts-api build/);
|
|
754
|
-
|
|
755
|
-
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
756
|
-
assert.match(apiEnv, /JWT_ACCESS_SECRET=/);
|
|
757
|
-
assert.match(apiEnv, /JWT_REFRESH_SECRET=/);
|
|
758
|
-
assert.match(apiEnv, /AUTH_ARGON2_MEMORY_COST=/);
|
|
759
|
-
assert.match(apiEnv, /AUTH_ARGON2_PARALLELISM=/);
|
|
760
|
-
|
|
761
|
-
const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
|
|
762
|
-
assert.match(compose, /JWT_ACCESS_SECRET: \$\{JWT_ACCESS_SECRET\}/);
|
|
763
|
-
assert.match(compose, /JWT_REFRESH_SECRET: \$\{JWT_REFRESH_SECRET\}/);
|
|
764
|
-
assert.match(compose, /AUTH_ARGON2_MEMORY_COST: \$\{AUTH_ARGON2_MEMORY_COST\}/);
|
|
765
|
-
|
|
766
|
-
const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
|
|
767
|
-
assert.match(readme, /## Accounts Module/);
|
|
768
|
-
assert.match(readme, /owner-scoped user routes/);
|
|
769
|
-
assert.match(readme, /AccountsEmailPort/);
|
|
770
|
-
|
|
771
|
-
const authServiceSource = fs.readFileSync(
|
|
772
|
-
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth.service.ts'),
|
|
773
|
-
'utf8',
|
|
774
|
-
);
|
|
775
|
-
assert.match(authServiceSource, /import type \{ RegisterRequest \} from '@forgeon\/accounts-contracts';/);
|
|
776
|
-
|
|
777
|
-
const authCoreSource = fs.readFileSync(
|
|
778
|
-
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth-core.service.ts'),
|
|
779
|
-
'utf8',
|
|
780
|
-
);
|
|
781
|
-
assert.match(authCoreSource, /AuthStore/);
|
|
782
|
-
assert.doesNotMatch(authCoreSource, /ACCOUNTS_PERSISTENCE_PORT/);
|
|
783
|
-
|
|
784
|
-
const accountsApiPackage = fs.readFileSync(
|
|
785
|
-
path.join(projectRoot, 'packages', 'accounts-api', 'package.json'),
|
|
786
|
-
'utf8',
|
|
787
|
-
);
|
|
788
|
-
assert.match(accountsApiPackage, /@forgeon\/db-prisma/);
|
|
789
|
-
|
|
790
|
-
const authStoreSource = fs.readFileSync(
|
|
791
|
-
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth.store.ts'),
|
|
792
|
-
'utf8',
|
|
793
|
-
);
|
|
794
|
-
assert.match(authStoreSource, /PrismaService/);
|
|
795
|
-
assert.match(authStoreSource, /authRefreshToken\.updateMany/);
|
|
796
|
-
|
|
797
|
-
const usersStoreSource = fs.readFileSync(
|
|
798
|
-
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'users.store.ts'),
|
|
799
|
-
'utf8',
|
|
800
|
-
);
|
|
801
|
-
assert.match(usersStoreSource, /PrismaService/);
|
|
802
|
-
assert.match(usersStoreSource, /userProfile\.upsert/);
|
|
803
|
-
|
|
804
|
-
const prismaStorePath = path.join(
|
|
805
|
-
projectRoot,
|
|
806
|
-
'apps',
|
|
807
|
-
'api',
|
|
808
|
-
'src',
|
|
809
|
-
'accounts',
|
|
810
|
-
'prisma-accounts-persistence.store.ts',
|
|
811
|
-
);
|
|
812
|
-
assert.equal(fs.existsSync(prismaStorePath), false);
|
|
813
|
-
}
|
|
712
|
+
function assertAccountsWiring(projectRoot) {
|
|
713
|
+
const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
|
|
714
|
+
assert.match(apiPackage, /@forgeon\/accounts-api/);
|
|
715
|
+
assert.match(apiPackage, /@forgeon\/accounts-contracts/);
|
|
716
|
+
assert.match(apiPackage, /pnpm --filter @forgeon\/accounts-contracts build/);
|
|
717
|
+
assert.match(apiPackage, /pnpm --filter @forgeon\/accounts-api build/);
|
|
718
|
+
|
|
719
|
+
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
720
|
+
assert.match(appModule, /authConfig/);
|
|
721
|
+
assert.match(appModule, /authEnvSchema/);
|
|
722
|
+
assert.match(appModule, /ForgeonAccountsModule\.register\(\{/);
|
|
723
|
+
assert.match(appModule, /UsersModule\.register\(\{\}\)/);
|
|
724
|
+
assert.doesNotMatch(appModule, /ACCOUNTS_PERSISTENCE_PORT/);
|
|
725
|
+
assert.doesNotMatch(appModule, /PrismaAccountsPersistenceStore/);
|
|
726
|
+
assert.doesNotMatch(appModule, /AUTH_REFRESH_TOKEN_STORE/);
|
|
727
|
+
|
|
728
|
+
const healthController = fs.readFileSync(
|
|
729
|
+
path.join(projectRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts'),
|
|
730
|
+
'utf8',
|
|
731
|
+
);
|
|
732
|
+
assert.match(healthController, /@Get\('auth'\)/);
|
|
733
|
+
assert.match(healthController, /authService\.getProbeStatus/);
|
|
734
|
+
assert.match(healthController, /\$queryRaw/);
|
|
735
|
+
assert.doesNotMatch(healthController, /data:\s*\{\s*email\s*\}/);
|
|
736
|
+
assert.doesNotMatch(healthController, /,\s*,/);
|
|
737
|
+
|
|
738
|
+
assertWebProbeShell(projectRoot);
|
|
739
|
+
const probesTs = readWebProbes(projectRoot);
|
|
740
|
+
assert.match(probesTs, /"id": "auth"/);
|
|
741
|
+
assert.match(probesTs, /"buttonLabel": "Check accounts probe"/);
|
|
742
|
+
assert.match(probesTs, /"resultTitle": "Accounts probe response"/);
|
|
743
|
+
|
|
744
|
+
const apiDockerfile = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'Dockerfile'), 'utf8');
|
|
745
|
+
assert.match(
|
|
746
|
+
apiDockerfile,
|
|
747
|
+
/COPY packages\/accounts-contracts\/package\.json packages\/accounts-contracts\/package\.json/,
|
|
748
|
+
);
|
|
749
|
+
assert.match(apiDockerfile, /COPY packages\/accounts-api\/package\.json packages\/accounts-api\/package\.json/);
|
|
750
|
+
assert.match(apiDockerfile, /COPY packages\/accounts-contracts packages\/accounts-contracts/);
|
|
751
|
+
assert.match(apiDockerfile, /COPY packages\/accounts-api packages\/accounts-api/);
|
|
752
|
+
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/accounts-contracts build/);
|
|
753
|
+
assert.match(apiDockerfile, /RUN pnpm --filter @forgeon\/accounts-api build/);
|
|
754
|
+
|
|
755
|
+
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
756
|
+
assert.match(apiEnv, /JWT_ACCESS_SECRET=/);
|
|
757
|
+
assert.match(apiEnv, /JWT_REFRESH_SECRET=/);
|
|
758
|
+
assert.match(apiEnv, /AUTH_ARGON2_MEMORY_COST=/);
|
|
759
|
+
assert.match(apiEnv, /AUTH_ARGON2_PARALLELISM=/);
|
|
760
|
+
|
|
761
|
+
const compose = fs.readFileSync(path.join(projectRoot, 'infra', 'docker', 'compose.yml'), 'utf8');
|
|
762
|
+
assert.match(compose, /JWT_ACCESS_SECRET: \$\{JWT_ACCESS_SECRET\}/);
|
|
763
|
+
assert.match(compose, /JWT_REFRESH_SECRET: \$\{JWT_REFRESH_SECRET\}/);
|
|
764
|
+
assert.match(compose, /AUTH_ARGON2_MEMORY_COST: \$\{AUTH_ARGON2_MEMORY_COST\}/);
|
|
765
|
+
|
|
766
|
+
const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
|
|
767
|
+
assert.match(readme, /## Accounts Module/);
|
|
768
|
+
assert.match(readme, /owner-scoped user routes/);
|
|
769
|
+
assert.match(readme, /AccountsEmailPort/);
|
|
770
|
+
|
|
771
|
+
const authServiceSource = fs.readFileSync(
|
|
772
|
+
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth.service.ts'),
|
|
773
|
+
'utf8',
|
|
774
|
+
);
|
|
775
|
+
assert.match(authServiceSource, /import type \{ RegisterRequest \} from '@forgeon\/accounts-contracts';/);
|
|
776
|
+
|
|
777
|
+
const authCoreSource = fs.readFileSync(
|
|
778
|
+
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth-core.service.ts'),
|
|
779
|
+
'utf8',
|
|
780
|
+
);
|
|
781
|
+
assert.match(authCoreSource, /AuthStore/);
|
|
782
|
+
assert.doesNotMatch(authCoreSource, /ACCOUNTS_PERSISTENCE_PORT/);
|
|
783
|
+
|
|
784
|
+
const accountsApiPackage = fs.readFileSync(
|
|
785
|
+
path.join(projectRoot, 'packages', 'accounts-api', 'package.json'),
|
|
786
|
+
'utf8',
|
|
787
|
+
);
|
|
788
|
+
assert.match(accountsApiPackage, /@forgeon\/db-prisma/);
|
|
789
|
+
|
|
790
|
+
const authStoreSource = fs.readFileSync(
|
|
791
|
+
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth.store.ts'),
|
|
792
|
+
'utf8',
|
|
793
|
+
);
|
|
794
|
+
assert.match(authStoreSource, /PrismaService/);
|
|
795
|
+
assert.match(authStoreSource, /authRefreshToken\.updateMany/);
|
|
796
|
+
|
|
797
|
+
const usersStoreSource = fs.readFileSync(
|
|
798
|
+
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'users.store.ts'),
|
|
799
|
+
'utf8',
|
|
800
|
+
);
|
|
801
|
+
assert.match(usersStoreSource, /PrismaService/);
|
|
802
|
+
assert.match(usersStoreSource, /userProfile\.upsert/);
|
|
803
|
+
|
|
804
|
+
const prismaStorePath = path.join(
|
|
805
|
+
projectRoot,
|
|
806
|
+
'apps',
|
|
807
|
+
'api',
|
|
808
|
+
'src',
|
|
809
|
+
'accounts',
|
|
810
|
+
'prisma-accounts-persistence.store.ts',
|
|
811
|
+
);
|
|
812
|
+
assert.equal(fs.existsSync(prismaStorePath), false);
|
|
813
|
+
}
|
|
814
814
|
function stripDbPrismaArtifacts(projectRoot) {
|
|
815
815
|
const dbPackageDir = path.join(projectRoot, 'packages', 'db-prisma');
|
|
816
816
|
if (fs.existsSync(dbPackageDir)) {
|
|
@@ -1573,21 +1573,21 @@ describe('addModule', () => {
|
|
|
1573
1573
|
packageRoot,
|
|
1574
1574
|
});
|
|
1575
1575
|
|
|
1576
|
-
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
1577
|
-
assert.match(apiEnv, /FILES_STORAGE_DRIVER=s3/);
|
|
1578
|
-
|
|
1579
|
-
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
1580
|
-
assert.match(appModule, /ForgeonFilesS3StorageModule/);
|
|
1581
|
-
assert.doesNotMatch(appModule, /ForgeonFilesLocalStorageModule/);
|
|
1582
|
-
|
|
1583
|
-
const filesService = fs.readFileSync(
|
|
1584
|
-
path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
|
|
1585
|
-
'utf8',
|
|
1586
|
-
);
|
|
1587
|
-
assert.doesNotMatch(filesService, /storeS3/);
|
|
1588
|
-
assert.doesNotMatch(filesService, /openS3/);
|
|
1589
|
-
assert.doesNotMatch(filesService, /deleteS3/);
|
|
1590
|
-
assert.doesNotMatch(filesService, /@aws-sdk\/client-s3/);
|
|
1576
|
+
const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
|
|
1577
|
+
assert.match(apiEnv, /FILES_STORAGE_DRIVER=s3/);
|
|
1578
|
+
|
|
1579
|
+
const appModule = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'app.module.ts'), 'utf8');
|
|
1580
|
+
assert.match(appModule, /ForgeonFilesS3StorageModule/);
|
|
1581
|
+
assert.doesNotMatch(appModule, /ForgeonFilesLocalStorageModule/);
|
|
1582
|
+
|
|
1583
|
+
const filesService = fs.readFileSync(
|
|
1584
|
+
path.join(projectRoot, 'packages', 'files', 'src', 'files.service.ts'),
|
|
1585
|
+
'utf8',
|
|
1586
|
+
);
|
|
1587
|
+
assert.doesNotMatch(filesService, /storeS3/);
|
|
1588
|
+
assert.doesNotMatch(filesService, /openS3/);
|
|
1589
|
+
assert.doesNotMatch(filesService, /deleteS3/);
|
|
1590
|
+
assert.doesNotMatch(filesService, /@aws-sdk\/client-s3/);
|
|
1591
1591
|
} finally {
|
|
1592
1592
|
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
1593
1593
|
}
|
|
@@ -2197,178 +2197,178 @@ describe('addModule', () => {
|
|
|
2197
2197
|
}
|
|
2198
2198
|
});
|
|
2199
2199
|
|
|
2200
|
-
it('applies accounts with db-prisma and wires the DB-backed runtime immediately', () => {
|
|
2201
|
-
const targetRoot = mkTmp('forgeon-module-accounts-db-');
|
|
2202
|
-
const projectRoot = path.join(targetRoot, 'demo-accounts-db');
|
|
2203
|
-
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
2204
|
-
|
|
2205
|
-
try {
|
|
2206
|
-
scaffoldProject({
|
|
2207
|
-
templateRoot,
|
|
2208
|
-
packageRoot,
|
|
2209
|
-
targetRoot: projectRoot,
|
|
2210
|
-
projectName: 'demo-accounts-db',
|
|
2211
|
-
frontend: 'react',
|
|
2212
|
-
db: 'prisma',
|
|
2213
|
-
dbPrismaEnabled: true,
|
|
2214
|
-
i18nEnabled: true,
|
|
2215
|
-
proxy: 'caddy',
|
|
2216
|
-
});
|
|
2217
|
-
|
|
2218
|
-
const result = addModule({
|
|
2219
|
-
moduleId: 'accounts',
|
|
2220
|
-
targetRoot: projectRoot,
|
|
2221
|
-
packageRoot,
|
|
2222
|
-
});
|
|
2223
|
-
|
|
2224
|
-
assert.equal(result.applied, true);
|
|
2225
|
-
assertAccountsWiring(projectRoot);
|
|
2226
|
-
|
|
2227
|
-
const schema = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'prisma', 'schema.prisma'), 'utf8');
|
|
2228
|
-
assert.match(schema, /model UserProfile/);
|
|
2229
|
-
assert.match(schema, /model UserSettings/);
|
|
2230
|
-
assert.match(schema, /model AuthIdentity/);
|
|
2231
|
-
assert.match(schema, /model AuthCredential/);
|
|
2232
|
-
assert.match(schema, /model AuthRefreshToken/);
|
|
2233
|
-
assert.doesNotMatch(schema, /roles\s+/i);
|
|
2234
|
-
assert.doesNotMatch(schema, /permissions\s+/i);
|
|
2235
|
-
|
|
2236
|
-
const migrationPath = path.join(
|
|
2237
|
-
projectRoot,
|
|
2238
|
-
'apps',
|
|
2239
|
-
'api',
|
|
2240
|
-
'prisma',
|
|
2241
|
-
'migrations',
|
|
2242
|
-
'0002_accounts_core',
|
|
2243
|
-
'migration.sql',
|
|
2244
|
-
);
|
|
2245
|
-
assert.equal(fs.existsSync(migrationPath), true);
|
|
2246
|
-
|
|
2247
|
-
const seedSource = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'prisma', 'seed.ts'), 'utf8');
|
|
2248
|
-
assert.match(seedSource, /Prisma\.dmmf/);
|
|
2249
|
-
assert.match(seedSource, /userFields\.has\('email'\)/);
|
|
2250
|
-
|
|
2251
|
-
const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
|
|
2252
|
-
assert.match(readme, /POST \/api\/auth\/register/);
|
|
2253
|
-
assert.match(readme, /POST \/api\/auth\/password-reset\/request/);
|
|
2254
|
-
assert.match(readme, /\/api\/users\/:id\/settings/);
|
|
2255
|
-
|
|
2256
|
-
const moduleDoc = fs.readFileSync(result.docsPath, 'utf8');
|
|
2257
|
-
assert.match(moduleDoc, /Status: implemented/);
|
|
2258
|
-
assert.match(moduleDoc, /db-adapter/);
|
|
2259
|
-
assert.match(moduleDoc, /owner-scoped/);
|
|
2260
|
-
} finally {
|
|
2261
|
-
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
2262
|
-
}
|
|
2263
|
-
});
|
|
2264
|
-
|
|
2265
|
-
it('detects and applies accounts-rbac compatibility sync explicitly', () => {
|
|
2266
|
-
const targetRoot = mkTmp('forgeon-module-accounts-rbac-');
|
|
2267
|
-
const projectRoot = path.join(targetRoot, 'demo-accounts-rbac');
|
|
2268
|
-
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
2269
|
-
|
|
2270
|
-
try {
|
|
2271
|
-
scaffoldProject({
|
|
2272
|
-
templateRoot,
|
|
2273
|
-
packageRoot,
|
|
2274
|
-
targetRoot: projectRoot,
|
|
2275
|
-
projectName: 'demo-accounts-rbac',
|
|
2276
|
-
frontend: 'react',
|
|
2277
|
-
db: 'prisma',
|
|
2278
|
-
dbPrismaEnabled: true,
|
|
2279
|
-
i18nEnabled: false,
|
|
2280
|
-
proxy: 'caddy',
|
|
2281
|
-
});
|
|
2282
|
-
|
|
2283
|
-
addModule({
|
|
2284
|
-
moduleId: 'rbac',
|
|
2285
|
-
targetRoot: projectRoot,
|
|
2286
|
-
packageRoot,
|
|
2287
|
-
});
|
|
2288
|
-
addModule({
|
|
2289
|
-
moduleId: 'accounts',
|
|
2290
|
-
targetRoot: projectRoot,
|
|
2291
|
-
packageRoot,
|
|
2292
|
-
});
|
|
2293
|
-
|
|
2294
|
-
const scan = scanIntegrations({
|
|
2295
|
-
targetRoot: projectRoot,
|
|
2296
|
-
relatedModuleId: 'accounts',
|
|
2297
|
-
});
|
|
2298
|
-
assert.equal(scan.groups.some((group) => group.id === 'accounts-rbac'), true);
|
|
2299
|
-
|
|
2300
|
-
const syncResult = syncIntegrations({
|
|
2301
|
-
targetRoot: projectRoot,
|
|
2302
|
-
packageRoot,
|
|
2303
|
-
groupIds: ['accounts-rbac'],
|
|
2304
|
-
});
|
|
2305
|
-
const claimsPair = syncResult.summary.find((item) => item.id === 'accounts-rbac');
|
|
2306
|
-
assert.ok(claimsPair);
|
|
2307
|
-
assert.equal(claimsPair.result.applied, true);
|
|
2308
|
-
|
|
2309
|
-
const contracts = fs.readFileSync(
|
|
2310
|
-
path.join(projectRoot, 'packages', 'accounts-contracts', 'src', 'index.ts'),
|
|
2311
|
-
'utf8',
|
|
2312
|
-
);
|
|
2313
|
-
assert.match(contracts, /roles\?: string\[\];/);
|
|
2314
|
-
assert.match(contracts, /permissions\?: string\[\];/);
|
|
2315
|
-
|
|
2316
|
-
const authTypes = fs.readFileSync(
|
|
2317
|
-
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth.types.ts'),
|
|
2318
|
-
'utf8',
|
|
2319
|
-
);
|
|
2320
|
-
assert.match(authTypes, /roles\?: string\[\];/);
|
|
2321
|
-
assert.match(authTypes, /permissions\?: string\[\];/);
|
|
2322
|
-
|
|
2323
|
-
const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
|
|
2324
|
-
assert.match(readme, /forgeon:accounts:rbac:start/);
|
|
2325
|
-
assert.match(readme, /base accounts schema remains free of roles and permissions/);
|
|
2326
|
-
} finally {
|
|
2327
|
-
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
2328
|
-
}
|
|
2329
|
-
});
|
|
2330
|
-
|
|
2331
|
-
it('scans accounts-rbac compatibility when accounts and rbac are both installed', () => {
|
|
2332
|
-
const targetRoot = mkTmp('forgeon-module-accounts-rbac-scan-');
|
|
2333
|
-
const projectRoot = path.join(targetRoot, 'demo-accounts-rbac-scan');
|
|
2334
|
-
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
2335
|
-
|
|
2336
|
-
try {
|
|
2337
|
-
scaffoldProject({
|
|
2338
|
-
templateRoot,
|
|
2339
|
-
packageRoot,
|
|
2340
|
-
targetRoot: projectRoot,
|
|
2341
|
-
projectName: 'demo-accounts-rbac-scan',
|
|
2342
|
-
frontend: 'react',
|
|
2343
|
-
db: 'prisma',
|
|
2344
|
-
dbPrismaEnabled: true,
|
|
2345
|
-
i18nEnabled: false,
|
|
2346
|
-
proxy: 'caddy',
|
|
2347
|
-
});
|
|
2348
|
-
|
|
2349
|
-
addModule({
|
|
2350
|
-
moduleId: 'accounts',
|
|
2351
|
-
targetRoot: projectRoot,
|
|
2352
|
-
packageRoot,
|
|
2353
|
-
});
|
|
2354
|
-
addModule({
|
|
2355
|
-
moduleId: 'rbac',
|
|
2356
|
-
targetRoot: projectRoot,
|
|
2357
|
-
packageRoot,
|
|
2358
|
-
});
|
|
2359
|
-
|
|
2360
|
-
const scan = scanIntegrations({
|
|
2361
|
-
targetRoot: projectRoot,
|
|
2362
|
-
relatedModuleId: 'rbac',
|
|
2363
|
-
});
|
|
2364
|
-
const compatibilityGroup = scan.groups.find((group) => group.id === 'accounts-rbac');
|
|
2365
|
-
|
|
2366
|
-
assert.ok(compatibilityGroup);
|
|
2367
|
-
assert.deepEqual(compatibilityGroup.modules, ['accounts', 'rbac']);
|
|
2368
|
-
} finally {
|
|
2369
|
-
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
2370
|
-
}
|
|
2371
|
-
});
|
|
2200
|
+
it('applies accounts with db-prisma and wires the DB-backed runtime immediately', () => {
|
|
2201
|
+
const targetRoot = mkTmp('forgeon-module-accounts-db-');
|
|
2202
|
+
const projectRoot = path.join(targetRoot, 'demo-accounts-db');
|
|
2203
|
+
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
2204
|
+
|
|
2205
|
+
try {
|
|
2206
|
+
scaffoldProject({
|
|
2207
|
+
templateRoot,
|
|
2208
|
+
packageRoot,
|
|
2209
|
+
targetRoot: projectRoot,
|
|
2210
|
+
projectName: 'demo-accounts-db',
|
|
2211
|
+
frontend: 'react',
|
|
2212
|
+
db: 'prisma',
|
|
2213
|
+
dbPrismaEnabled: true,
|
|
2214
|
+
i18nEnabled: true,
|
|
2215
|
+
proxy: 'caddy',
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
const result = addModule({
|
|
2219
|
+
moduleId: 'accounts',
|
|
2220
|
+
targetRoot: projectRoot,
|
|
2221
|
+
packageRoot,
|
|
2222
|
+
});
|
|
2223
|
+
|
|
2224
|
+
assert.equal(result.applied, true);
|
|
2225
|
+
assertAccountsWiring(projectRoot);
|
|
2226
|
+
|
|
2227
|
+
const schema = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'prisma', 'schema.prisma'), 'utf8');
|
|
2228
|
+
assert.match(schema, /model UserProfile/);
|
|
2229
|
+
assert.match(schema, /model UserSettings/);
|
|
2230
|
+
assert.match(schema, /model AuthIdentity/);
|
|
2231
|
+
assert.match(schema, /model AuthCredential/);
|
|
2232
|
+
assert.match(schema, /model AuthRefreshToken/);
|
|
2233
|
+
assert.doesNotMatch(schema, /roles\s+/i);
|
|
2234
|
+
assert.doesNotMatch(schema, /permissions\s+/i);
|
|
2235
|
+
|
|
2236
|
+
const migrationPath = path.join(
|
|
2237
|
+
projectRoot,
|
|
2238
|
+
'apps',
|
|
2239
|
+
'api',
|
|
2240
|
+
'prisma',
|
|
2241
|
+
'migrations',
|
|
2242
|
+
'0002_accounts_core',
|
|
2243
|
+
'migration.sql',
|
|
2244
|
+
);
|
|
2245
|
+
assert.equal(fs.existsSync(migrationPath), true);
|
|
2246
|
+
|
|
2247
|
+
const seedSource = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'prisma', 'seed.ts'), 'utf8');
|
|
2248
|
+
assert.match(seedSource, /Prisma\.dmmf/);
|
|
2249
|
+
assert.match(seedSource, /userFields\.has\('email'\)/);
|
|
2250
|
+
|
|
2251
|
+
const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
|
|
2252
|
+
assert.match(readme, /POST \/api\/auth\/register/);
|
|
2253
|
+
assert.match(readme, /POST \/api\/auth\/password-reset\/request/);
|
|
2254
|
+
assert.match(readme, /\/api\/users\/:id\/settings/);
|
|
2255
|
+
|
|
2256
|
+
const moduleDoc = fs.readFileSync(result.docsPath, 'utf8');
|
|
2257
|
+
assert.match(moduleDoc, /Status: implemented/);
|
|
2258
|
+
assert.match(moduleDoc, /db-adapter/);
|
|
2259
|
+
assert.match(moduleDoc, /owner-scoped/);
|
|
2260
|
+
} finally {
|
|
2261
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
|
|
2265
|
+
it('detects and applies accounts-rbac compatibility sync explicitly', () => {
|
|
2266
|
+
const targetRoot = mkTmp('forgeon-module-accounts-rbac-');
|
|
2267
|
+
const projectRoot = path.join(targetRoot, 'demo-accounts-rbac');
|
|
2268
|
+
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
2269
|
+
|
|
2270
|
+
try {
|
|
2271
|
+
scaffoldProject({
|
|
2272
|
+
templateRoot,
|
|
2273
|
+
packageRoot,
|
|
2274
|
+
targetRoot: projectRoot,
|
|
2275
|
+
projectName: 'demo-accounts-rbac',
|
|
2276
|
+
frontend: 'react',
|
|
2277
|
+
db: 'prisma',
|
|
2278
|
+
dbPrismaEnabled: true,
|
|
2279
|
+
i18nEnabled: false,
|
|
2280
|
+
proxy: 'caddy',
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
addModule({
|
|
2284
|
+
moduleId: 'rbac',
|
|
2285
|
+
targetRoot: projectRoot,
|
|
2286
|
+
packageRoot,
|
|
2287
|
+
});
|
|
2288
|
+
addModule({
|
|
2289
|
+
moduleId: 'accounts',
|
|
2290
|
+
targetRoot: projectRoot,
|
|
2291
|
+
packageRoot,
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
const scan = scanIntegrations({
|
|
2295
|
+
targetRoot: projectRoot,
|
|
2296
|
+
relatedModuleId: 'accounts',
|
|
2297
|
+
});
|
|
2298
|
+
assert.equal(scan.groups.some((group) => group.id === 'accounts-rbac'), true);
|
|
2299
|
+
|
|
2300
|
+
const syncResult = syncIntegrations({
|
|
2301
|
+
targetRoot: projectRoot,
|
|
2302
|
+
packageRoot,
|
|
2303
|
+
groupIds: ['accounts-rbac'],
|
|
2304
|
+
});
|
|
2305
|
+
const claimsPair = syncResult.summary.find((item) => item.id === 'accounts-rbac');
|
|
2306
|
+
assert.ok(claimsPair);
|
|
2307
|
+
assert.equal(claimsPair.result.applied, true);
|
|
2308
|
+
|
|
2309
|
+
const contracts = fs.readFileSync(
|
|
2310
|
+
path.join(projectRoot, 'packages', 'accounts-contracts', 'src', 'index.ts'),
|
|
2311
|
+
'utf8',
|
|
2312
|
+
);
|
|
2313
|
+
assert.match(contracts, /roles\?: string\[\];/);
|
|
2314
|
+
assert.match(contracts, /permissions\?: string\[\];/);
|
|
2315
|
+
|
|
2316
|
+
const authTypes = fs.readFileSync(
|
|
2317
|
+
path.join(projectRoot, 'packages', 'accounts-api', 'src', 'auth.types.ts'),
|
|
2318
|
+
'utf8',
|
|
2319
|
+
);
|
|
2320
|
+
assert.match(authTypes, /roles\?: string\[\];/);
|
|
2321
|
+
assert.match(authTypes, /permissions\?: string\[\];/);
|
|
2322
|
+
|
|
2323
|
+
const readme = fs.readFileSync(path.join(projectRoot, 'README.md'), 'utf8');
|
|
2324
|
+
assert.match(readme, /forgeon:accounts:rbac:start/);
|
|
2325
|
+
assert.match(readme, /base accounts schema remains free of roles and permissions/);
|
|
2326
|
+
} finally {
|
|
2327
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
|
|
2331
|
+
it('scans accounts-rbac compatibility when accounts and rbac are both installed', () => {
|
|
2332
|
+
const targetRoot = mkTmp('forgeon-module-accounts-rbac-scan-');
|
|
2333
|
+
const projectRoot = path.join(targetRoot, 'demo-accounts-rbac-scan');
|
|
2334
|
+
const templateRoot = path.join(packageRoot, 'templates', 'base');
|
|
2335
|
+
|
|
2336
|
+
try {
|
|
2337
|
+
scaffoldProject({
|
|
2338
|
+
templateRoot,
|
|
2339
|
+
packageRoot,
|
|
2340
|
+
targetRoot: projectRoot,
|
|
2341
|
+
projectName: 'demo-accounts-rbac-scan',
|
|
2342
|
+
frontend: 'react',
|
|
2343
|
+
db: 'prisma',
|
|
2344
|
+
dbPrismaEnabled: true,
|
|
2345
|
+
i18nEnabled: false,
|
|
2346
|
+
proxy: 'caddy',
|
|
2347
|
+
});
|
|
2348
|
+
|
|
2349
|
+
addModule({
|
|
2350
|
+
moduleId: 'accounts',
|
|
2351
|
+
targetRoot: projectRoot,
|
|
2352
|
+
packageRoot,
|
|
2353
|
+
});
|
|
2354
|
+
addModule({
|
|
2355
|
+
moduleId: 'rbac',
|
|
2356
|
+
targetRoot: projectRoot,
|
|
2357
|
+
packageRoot,
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
const scan = scanIntegrations({
|
|
2361
|
+
targetRoot: projectRoot,
|
|
2362
|
+
relatedModuleId: 'rbac',
|
|
2363
|
+
});
|
|
2364
|
+
const compatibilityGroup = scan.groups.find((group) => group.id === 'accounts-rbac');
|
|
2365
|
+
|
|
2366
|
+
assert.ok(compatibilityGroup);
|
|
2367
|
+
assert.deepEqual(compatibilityGroup.modules, ['accounts', 'rbac']);
|
|
2368
|
+
} finally {
|
|
2369
|
+
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
2372
|
it('applies logger then accounts on db/i18n-disabled scaffold without breaking health controller syntax', () => {
|
|
2373
2373
|
const targetRoot = mkTmp('forgeon-module-jwt-nodb-noi18n-');
|
|
2374
2374
|
const projectRoot = path.join(targetRoot, 'demo-jwt-nodb-noi18n');
|