@saasicat/cli 0.11.0 → 0.12.0
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/saasicat.js +20 -0
- package/dist/index.cjs +63 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +61 -1
- package/package.json +4 -4
package/bin/saasicat.js
CHANGED
|
@@ -169,6 +169,26 @@ function printCheckReport(report) {
|
|
|
169
169
|
console.log('');
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
const breaking = report.missingBlockAttributes.filter((a) => a.kind !== 'index');
|
|
173
|
+
if (breaking.length > 0) {
|
|
174
|
+
console.log(`✗ Fehlende Constraints (${breaking.length}):`);
|
|
175
|
+
for (const { model, kind, expected, actual } of breaking) {
|
|
176
|
+
const suffix = kind === 'map' ? ` — vorhanden: @@map("${actual}")` : '';
|
|
177
|
+
console.log(` ${model.padEnd(28)} ${expected}${suffix}`);
|
|
178
|
+
}
|
|
179
|
+
console.log('');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const missingIndexes = report.missingBlockAttributes.filter((a) => a.kind === 'index');
|
|
183
|
+
if (missingIndexes.length > 0) {
|
|
184
|
+
console.log(`→ Fehlende Indizes (${missingIndexes.length}):`);
|
|
185
|
+
for (const { model, expected } of missingIndexes) {
|
|
186
|
+
console.log(` ${model.padEnd(28)} ${expected}`);
|
|
187
|
+
}
|
|
188
|
+
console.log(' Kein Fehler — kostet Query-Zeit, bricht aber nichts.');
|
|
189
|
+
console.log('');
|
|
190
|
+
}
|
|
191
|
+
|
|
172
192
|
const absent = [...report.absentModels, ...report.absentEnums];
|
|
173
193
|
if (absent.length > 0) {
|
|
174
194
|
console.log(`→ Nicht übernommen (${absent.length}): ${absent.join(', ')}`);
|
package/dist/index.cjs
CHANGED
|
@@ -68,11 +68,13 @@ __export(index_exports, {
|
|
|
68
68
|
WhoAmIFlow: () => WhoAmIFlow,
|
|
69
69
|
applyFragmentBlocks: () => applyFragmentBlocks,
|
|
70
70
|
blockBodyLines: () => blockBodyLines,
|
|
71
|
+
breaksContract: () => breaksContract,
|
|
71
72
|
checkSchema: () => checkSchema,
|
|
72
73
|
extractBlockNames: () => extractBlockNames,
|
|
73
74
|
extractBlocks: () => extractBlocks,
|
|
74
75
|
extractModelBlocks: () => extractModelBlocks,
|
|
75
76
|
extractModelNames: () => extractModelNames,
|
|
77
|
+
parseBlockAttributes: () => parseBlockAttributes,
|
|
76
78
|
parseEnumValues: () => parseEnumValues,
|
|
77
79
|
parseFields: () => parseFields,
|
|
78
80
|
parseSchema: () => parseSchema,
|
|
@@ -1245,10 +1247,27 @@ function parseEnumValues(block) {
|
|
|
1245
1247
|
return values;
|
|
1246
1248
|
}
|
|
1247
1249
|
__name(parseEnumValues, "parseEnumValues");
|
|
1250
|
+
function attributeFieldLists(block, attribute) {
|
|
1251
|
+
const pattern = new RegExp(`@@${attribute}\\(([^)]*\\])`, "g");
|
|
1252
|
+
return new Set([
|
|
1253
|
+
...block.matchAll(pattern)
|
|
1254
|
+
].map((match) => match[1].replace(/\s+/g, "")));
|
|
1255
|
+
}
|
|
1256
|
+
__name(attributeFieldLists, "attributeFieldLists");
|
|
1257
|
+
function parseBlockAttributes(name, block) {
|
|
1258
|
+
return {
|
|
1259
|
+
indexes: attributeFieldLists(block, "index"),
|
|
1260
|
+
uniques: attributeFieldLists(block, "unique"),
|
|
1261
|
+
map: block.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
__name(parseBlockAttributes, "parseBlockAttributes");
|
|
1248
1265
|
function parseSchema(schema) {
|
|
1249
1266
|
const models = /* @__PURE__ */ new Map();
|
|
1267
|
+
const modelAttributes = /* @__PURE__ */ new Map();
|
|
1250
1268
|
for (const [name, block] of extractBlocks(schema, "model")) {
|
|
1251
1269
|
models.set(name, parseFields(block));
|
|
1270
|
+
modelAttributes.set(name, parseBlockAttributes(name, block));
|
|
1252
1271
|
}
|
|
1253
1272
|
const enums = /* @__PURE__ */ new Map();
|
|
1254
1273
|
for (const [name, block] of extractBlocks(schema, "enum")) {
|
|
@@ -1256,6 +1275,7 @@ function parseSchema(schema) {
|
|
|
1256
1275
|
}
|
|
1257
1276
|
return {
|
|
1258
1277
|
models,
|
|
1278
|
+
modelAttributes,
|
|
1259
1279
|
enums
|
|
1260
1280
|
};
|
|
1261
1281
|
}
|
|
@@ -1292,12 +1312,46 @@ function compareFields(model, specFields, appFields, appEnums, missingFields, fi
|
|
|
1292
1312
|
}
|
|
1293
1313
|
}
|
|
1294
1314
|
__name(compareFields, "compareFields");
|
|
1315
|
+
function compareBlockAttributes(model, spec, app, out) {
|
|
1316
|
+
if (spec.map !== app.map) {
|
|
1317
|
+
out.push({
|
|
1318
|
+
model,
|
|
1319
|
+
kind: "map",
|
|
1320
|
+
expected: `@@map("${spec.map}")`,
|
|
1321
|
+
actual: app.map
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
for (const fields of spec.uniques) {
|
|
1325
|
+
if (!app.uniques.has(fields)) {
|
|
1326
|
+
out.push({
|
|
1327
|
+
model,
|
|
1328
|
+
kind: "unique",
|
|
1329
|
+
expected: `@@unique(${fields})`
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
for (const fields of spec.indexes) {
|
|
1334
|
+
if (!app.indexes.has(fields)) {
|
|
1335
|
+
out.push({
|
|
1336
|
+
model,
|
|
1337
|
+
kind: "index",
|
|
1338
|
+
expected: `@@index(${fields})`
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
__name(compareBlockAttributes, "compareBlockAttributes");
|
|
1344
|
+
function breaksContract(attribute) {
|
|
1345
|
+
return attribute.kind !== "index";
|
|
1346
|
+
}
|
|
1347
|
+
__name(breaksContract, "breaksContract");
|
|
1295
1348
|
function checkSchema(specSchema, appSchema) {
|
|
1296
1349
|
const spec = parseSchema(specSchema);
|
|
1297
1350
|
const app = parseSchema(appSchema);
|
|
1298
1351
|
const absentModels = [];
|
|
1299
1352
|
const missingFields = [];
|
|
1300
1353
|
const fieldMismatches = [];
|
|
1354
|
+
const missingBlockAttributes = [];
|
|
1301
1355
|
for (const [model, specFields] of spec.models) {
|
|
1302
1356
|
const appFields = app.models.get(model);
|
|
1303
1357
|
if (!appFields) {
|
|
@@ -1305,6 +1359,11 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1305
1359
|
continue;
|
|
1306
1360
|
}
|
|
1307
1361
|
compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
|
|
1362
|
+
const specAttrs = spec.modelAttributes.get(model);
|
|
1363
|
+
const appAttrs = app.modelAttributes.get(model);
|
|
1364
|
+
if (specAttrs && appAttrs) {
|
|
1365
|
+
compareBlockAttributes(model, specAttrs, appAttrs, missingBlockAttributes);
|
|
1366
|
+
}
|
|
1308
1367
|
}
|
|
1309
1368
|
const absentEnums = [];
|
|
1310
1369
|
const missingEnumValues = [];
|
|
@@ -1329,9 +1388,10 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1329
1388
|
missingFields,
|
|
1330
1389
|
missingEnumValues,
|
|
1331
1390
|
fieldMismatches,
|
|
1391
|
+
missingBlockAttributes,
|
|
1332
1392
|
checkedModelCount: spec.models.size - absentModels.length,
|
|
1333
1393
|
checkedEnumCount: spec.enums.size - absentEnums.length,
|
|
1334
|
-
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0
|
|
1394
|
+
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0 && !missingBlockAttributes.some(breaksContract)
|
|
1335
1395
|
};
|
|
1336
1396
|
}
|
|
1337
1397
|
__name(checkSchema, "checkSchema");
|
|
@@ -2426,11 +2486,13 @@ UserCommands = _ts_decorate14([
|
|
|
2426
2486
|
WhoAmIFlow,
|
|
2427
2487
|
applyFragmentBlocks,
|
|
2428
2488
|
blockBodyLines,
|
|
2489
|
+
breaksContract,
|
|
2429
2490
|
checkSchema,
|
|
2430
2491
|
extractBlockNames,
|
|
2431
2492
|
extractBlocks,
|
|
2432
2493
|
extractModelBlocks,
|
|
2433
2494
|
extractModelNames,
|
|
2495
|
+
parseBlockAttributes,
|
|
2434
2496
|
parseEnumValues,
|
|
2435
2497
|
parseFields,
|
|
2436
2498
|
parseSchema,
|
package/dist/index.d.cts
CHANGED
|
@@ -383,8 +383,17 @@ interface FieldSignature {
|
|
|
383
383
|
optional: boolean;
|
|
384
384
|
list: boolean;
|
|
385
385
|
}
|
|
386
|
+
interface BlockAttributes {
|
|
387
|
+
/** Field lists of `@@index`, normalised to `[a,b]` — attribute options dropped. */
|
|
388
|
+
indexes: Set<string>;
|
|
389
|
+
/** Same for `@@unique`. */
|
|
390
|
+
uniques: Set<string>;
|
|
391
|
+
/** `@@map` target, or the model name when unmapped. */
|
|
392
|
+
map: string;
|
|
393
|
+
}
|
|
386
394
|
interface ParsedSchema {
|
|
387
395
|
models: Map<string, Map<string, FieldSignature>>;
|
|
396
|
+
modelAttributes: Map<string, BlockAttributes>;
|
|
388
397
|
enums: Map<string, string[]>;
|
|
389
398
|
}
|
|
390
399
|
interface MissingField {
|
|
@@ -405,6 +414,15 @@ interface FieldMismatch {
|
|
|
405
414
|
expected: string;
|
|
406
415
|
actual: string;
|
|
407
416
|
}
|
|
417
|
+
type BlockAttributeKind = 'index' | 'unique' | 'map';
|
|
418
|
+
interface MissingBlockAttribute {
|
|
419
|
+
model: string;
|
|
420
|
+
kind: BlockAttributeKind;
|
|
421
|
+
/** Rendered attribute, e.g. `@@index([planId, validFrom])`. */
|
|
422
|
+
expected: string;
|
|
423
|
+
/** For `map`: what the consumer maps to instead. */
|
|
424
|
+
actual?: string;
|
|
425
|
+
}
|
|
408
426
|
interface SchemaCheckReport {
|
|
409
427
|
/** Platform models the consumer does not carry — informational. */
|
|
410
428
|
absentModels: string[];
|
|
@@ -413,6 +431,11 @@ interface SchemaCheckReport {
|
|
|
413
431
|
missingFields: MissingField[];
|
|
414
432
|
missingEnumValues: MissingEnumValue[];
|
|
415
433
|
fieldMismatches: FieldMismatch[];
|
|
434
|
+
/**
|
|
435
|
+
* Block-level attributes the spec declares and the consumer lacks:
|
|
436
|
+
* `@@index`, `@@unique`, and a diverging `@@map`.
|
|
437
|
+
*/
|
|
438
|
+
missingBlockAttributes: MissingBlockAttribute[];
|
|
416
439
|
/** Models present in both schemas, i.e. actually compared. */
|
|
417
440
|
checkedModelCount: number;
|
|
418
441
|
/** Enums present in both schemas, i.e. actually compared. */
|
|
@@ -427,8 +450,21 @@ declare function parseFields(block: string): Map<string, FieldSignature>;
|
|
|
427
450
|
* (`enum Role { ADMIN USER }`); a trailing `@map(…)` attribute is not a value.
|
|
428
451
|
*/
|
|
429
452
|
declare function parseEnumValues(block: string): string[];
|
|
453
|
+
/**
|
|
454
|
+
* Parses the block-level attributes of a `model`. Comparing these is what
|
|
455
|
+
* catches a missing index or unique constraint — differences the field-level
|
|
456
|
+
* comparison is blind to.
|
|
457
|
+
*/
|
|
458
|
+
declare function parseBlockAttributes(name: string, block: string): BlockAttributes;
|
|
430
459
|
/** Parses every `model` and `enum` declaration of a Prisma schema. */
|
|
431
460
|
declare function parseSchema(schema: string): ParsedSchema;
|
|
461
|
+
/**
|
|
462
|
+
* A missing index costs query time; a missing `@@unique` or a diverging
|
|
463
|
+
* `@@map` breaks correctness — the platform relies on the constraint holding,
|
|
464
|
+
* and on finding the table under its canonical name. Only the latter two fail
|
|
465
|
+
* the check.
|
|
466
|
+
*/
|
|
467
|
+
declare function breaksContract(attribute: MissingBlockAttribute): boolean;
|
|
432
468
|
/**
|
|
433
469
|
* Compares a consumer schema against the canonical fragments. `specSchema` is
|
|
434
470
|
* the concatenation of the fragments the check should cover.
|
|
@@ -625,4 +661,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
625
661
|
parsePassword(val: string): string;
|
|
626
662
|
}
|
|
627
663
|
|
|
628
|
-
export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, CLI_CONTEXT_CONFIG_TOKEN, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type FieldMismatch, type FieldMismatchReason, type FieldSignature, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingEnumValue, type MissingField, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, PlanCatalogDoctorCheck, type SchemaCheckReport, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, applyFragmentBlocks, blockBodyLines, checkSchema, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, parseEnumValues, parseFields, parseSchema, stripLineComment };
|
|
664
|
+
export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, type BlockAttributeKind, type BlockAttributes, CLI_CONTEXT_CONFIG_TOKEN, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type FieldMismatch, type FieldMismatchReason, type FieldSignature, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, PlanCatalogDoctorCheck, type SchemaCheckReport, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, applyFragmentBlocks, blockBodyLines, breaksContract, checkSchema, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, parseBlockAttributes, parseEnumValues, parseFields, parseSchema, stripLineComment };
|
package/dist/index.d.ts
CHANGED
|
@@ -383,8 +383,17 @@ interface FieldSignature {
|
|
|
383
383
|
optional: boolean;
|
|
384
384
|
list: boolean;
|
|
385
385
|
}
|
|
386
|
+
interface BlockAttributes {
|
|
387
|
+
/** Field lists of `@@index`, normalised to `[a,b]` — attribute options dropped. */
|
|
388
|
+
indexes: Set<string>;
|
|
389
|
+
/** Same for `@@unique`. */
|
|
390
|
+
uniques: Set<string>;
|
|
391
|
+
/** `@@map` target, or the model name when unmapped. */
|
|
392
|
+
map: string;
|
|
393
|
+
}
|
|
386
394
|
interface ParsedSchema {
|
|
387
395
|
models: Map<string, Map<string, FieldSignature>>;
|
|
396
|
+
modelAttributes: Map<string, BlockAttributes>;
|
|
388
397
|
enums: Map<string, string[]>;
|
|
389
398
|
}
|
|
390
399
|
interface MissingField {
|
|
@@ -405,6 +414,15 @@ interface FieldMismatch {
|
|
|
405
414
|
expected: string;
|
|
406
415
|
actual: string;
|
|
407
416
|
}
|
|
417
|
+
type BlockAttributeKind = 'index' | 'unique' | 'map';
|
|
418
|
+
interface MissingBlockAttribute {
|
|
419
|
+
model: string;
|
|
420
|
+
kind: BlockAttributeKind;
|
|
421
|
+
/** Rendered attribute, e.g. `@@index([planId, validFrom])`. */
|
|
422
|
+
expected: string;
|
|
423
|
+
/** For `map`: what the consumer maps to instead. */
|
|
424
|
+
actual?: string;
|
|
425
|
+
}
|
|
408
426
|
interface SchemaCheckReport {
|
|
409
427
|
/** Platform models the consumer does not carry — informational. */
|
|
410
428
|
absentModels: string[];
|
|
@@ -413,6 +431,11 @@ interface SchemaCheckReport {
|
|
|
413
431
|
missingFields: MissingField[];
|
|
414
432
|
missingEnumValues: MissingEnumValue[];
|
|
415
433
|
fieldMismatches: FieldMismatch[];
|
|
434
|
+
/**
|
|
435
|
+
* Block-level attributes the spec declares and the consumer lacks:
|
|
436
|
+
* `@@index`, `@@unique`, and a diverging `@@map`.
|
|
437
|
+
*/
|
|
438
|
+
missingBlockAttributes: MissingBlockAttribute[];
|
|
416
439
|
/** Models present in both schemas, i.e. actually compared. */
|
|
417
440
|
checkedModelCount: number;
|
|
418
441
|
/** Enums present in both schemas, i.e. actually compared. */
|
|
@@ -427,8 +450,21 @@ declare function parseFields(block: string): Map<string, FieldSignature>;
|
|
|
427
450
|
* (`enum Role { ADMIN USER }`); a trailing `@map(…)` attribute is not a value.
|
|
428
451
|
*/
|
|
429
452
|
declare function parseEnumValues(block: string): string[];
|
|
453
|
+
/**
|
|
454
|
+
* Parses the block-level attributes of a `model`. Comparing these is what
|
|
455
|
+
* catches a missing index or unique constraint — differences the field-level
|
|
456
|
+
* comparison is blind to.
|
|
457
|
+
*/
|
|
458
|
+
declare function parseBlockAttributes(name: string, block: string): BlockAttributes;
|
|
430
459
|
/** Parses every `model` and `enum` declaration of a Prisma schema. */
|
|
431
460
|
declare function parseSchema(schema: string): ParsedSchema;
|
|
461
|
+
/**
|
|
462
|
+
* A missing index costs query time; a missing `@@unique` or a diverging
|
|
463
|
+
* `@@map` breaks correctness — the platform relies on the constraint holding,
|
|
464
|
+
* and on finding the table under its canonical name. Only the latter two fail
|
|
465
|
+
* the check.
|
|
466
|
+
*/
|
|
467
|
+
declare function breaksContract(attribute: MissingBlockAttribute): boolean;
|
|
432
468
|
/**
|
|
433
469
|
* Compares a consumer schema against the canonical fragments. `specSchema` is
|
|
434
470
|
* the concatenation of the fragments the check should cover.
|
|
@@ -625,4 +661,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
625
661
|
parsePassword(val: string): string;
|
|
626
662
|
}
|
|
627
663
|
|
|
628
|
-
export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, CLI_CONTEXT_CONFIG_TOKEN, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type FieldMismatch, type FieldMismatchReason, type FieldSignature, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingEnumValue, type MissingField, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, PlanCatalogDoctorCheck, type SchemaCheckReport, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, applyFragmentBlocks, blockBodyLines, checkSchema, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, parseEnumValues, parseFields, parseSchema, stripLineComment };
|
|
664
|
+
export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, type BlockAttributeKind, type BlockAttributes, CLI_CONTEXT_CONFIG_TOKEN, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type FieldMismatch, type FieldMismatchReason, type FieldSignature, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, PlanCatalogDoctorCheck, type SchemaCheckReport, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, applyFragmentBlocks, blockBodyLines, breaksContract, checkSchema, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, parseBlockAttributes, parseEnumValues, parseFields, parseSchema, stripLineComment };
|
package/dist/index.js
CHANGED
|
@@ -1166,10 +1166,27 @@ function parseEnumValues(block) {
|
|
|
1166
1166
|
return values;
|
|
1167
1167
|
}
|
|
1168
1168
|
__name(parseEnumValues, "parseEnumValues");
|
|
1169
|
+
function attributeFieldLists(block, attribute) {
|
|
1170
|
+
const pattern = new RegExp(`@@${attribute}\\(([^)]*\\])`, "g");
|
|
1171
|
+
return new Set([
|
|
1172
|
+
...block.matchAll(pattern)
|
|
1173
|
+
].map((match) => match[1].replace(/\s+/g, "")));
|
|
1174
|
+
}
|
|
1175
|
+
__name(attributeFieldLists, "attributeFieldLists");
|
|
1176
|
+
function parseBlockAttributes(name, block) {
|
|
1177
|
+
return {
|
|
1178
|
+
indexes: attributeFieldLists(block, "index"),
|
|
1179
|
+
uniques: attributeFieldLists(block, "unique"),
|
|
1180
|
+
map: block.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
__name(parseBlockAttributes, "parseBlockAttributes");
|
|
1169
1184
|
function parseSchema(schema) {
|
|
1170
1185
|
const models = /* @__PURE__ */ new Map();
|
|
1186
|
+
const modelAttributes = /* @__PURE__ */ new Map();
|
|
1171
1187
|
for (const [name, block] of extractBlocks(schema, "model")) {
|
|
1172
1188
|
models.set(name, parseFields(block));
|
|
1189
|
+
modelAttributes.set(name, parseBlockAttributes(name, block));
|
|
1173
1190
|
}
|
|
1174
1191
|
const enums = /* @__PURE__ */ new Map();
|
|
1175
1192
|
for (const [name, block] of extractBlocks(schema, "enum")) {
|
|
@@ -1177,6 +1194,7 @@ function parseSchema(schema) {
|
|
|
1177
1194
|
}
|
|
1178
1195
|
return {
|
|
1179
1196
|
models,
|
|
1197
|
+
modelAttributes,
|
|
1180
1198
|
enums
|
|
1181
1199
|
};
|
|
1182
1200
|
}
|
|
@@ -1213,12 +1231,46 @@ function compareFields(model, specFields, appFields, appEnums, missingFields, fi
|
|
|
1213
1231
|
}
|
|
1214
1232
|
}
|
|
1215
1233
|
__name(compareFields, "compareFields");
|
|
1234
|
+
function compareBlockAttributes(model, spec, app, out) {
|
|
1235
|
+
if (spec.map !== app.map) {
|
|
1236
|
+
out.push({
|
|
1237
|
+
model,
|
|
1238
|
+
kind: "map",
|
|
1239
|
+
expected: `@@map("${spec.map}")`,
|
|
1240
|
+
actual: app.map
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
for (const fields of spec.uniques) {
|
|
1244
|
+
if (!app.uniques.has(fields)) {
|
|
1245
|
+
out.push({
|
|
1246
|
+
model,
|
|
1247
|
+
kind: "unique",
|
|
1248
|
+
expected: `@@unique(${fields})`
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
for (const fields of spec.indexes) {
|
|
1253
|
+
if (!app.indexes.has(fields)) {
|
|
1254
|
+
out.push({
|
|
1255
|
+
model,
|
|
1256
|
+
kind: "index",
|
|
1257
|
+
expected: `@@index(${fields})`
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
__name(compareBlockAttributes, "compareBlockAttributes");
|
|
1263
|
+
function breaksContract(attribute) {
|
|
1264
|
+
return attribute.kind !== "index";
|
|
1265
|
+
}
|
|
1266
|
+
__name(breaksContract, "breaksContract");
|
|
1216
1267
|
function checkSchema(specSchema, appSchema) {
|
|
1217
1268
|
const spec = parseSchema(specSchema);
|
|
1218
1269
|
const app = parseSchema(appSchema);
|
|
1219
1270
|
const absentModels = [];
|
|
1220
1271
|
const missingFields = [];
|
|
1221
1272
|
const fieldMismatches = [];
|
|
1273
|
+
const missingBlockAttributes = [];
|
|
1222
1274
|
for (const [model, specFields] of spec.models) {
|
|
1223
1275
|
const appFields = app.models.get(model);
|
|
1224
1276
|
if (!appFields) {
|
|
@@ -1226,6 +1278,11 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1226
1278
|
continue;
|
|
1227
1279
|
}
|
|
1228
1280
|
compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
|
|
1281
|
+
const specAttrs = spec.modelAttributes.get(model);
|
|
1282
|
+
const appAttrs = app.modelAttributes.get(model);
|
|
1283
|
+
if (specAttrs && appAttrs) {
|
|
1284
|
+
compareBlockAttributes(model, specAttrs, appAttrs, missingBlockAttributes);
|
|
1285
|
+
}
|
|
1229
1286
|
}
|
|
1230
1287
|
const absentEnums = [];
|
|
1231
1288
|
const missingEnumValues = [];
|
|
@@ -1250,9 +1307,10 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1250
1307
|
missingFields,
|
|
1251
1308
|
missingEnumValues,
|
|
1252
1309
|
fieldMismatches,
|
|
1310
|
+
missingBlockAttributes,
|
|
1253
1311
|
checkedModelCount: spec.models.size - absentModels.length,
|
|
1254
1312
|
checkedEnumCount: spec.enums.size - absentEnums.length,
|
|
1255
|
-
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0
|
|
1313
|
+
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0 && !missingBlockAttributes.some(breaksContract)
|
|
1256
1314
|
};
|
|
1257
1315
|
}
|
|
1258
1316
|
__name(checkSchema, "checkSchema");
|
|
@@ -2346,11 +2404,13 @@ export {
|
|
|
2346
2404
|
WhoAmIFlow,
|
|
2347
2405
|
applyFragmentBlocks,
|
|
2348
2406
|
blockBodyLines,
|
|
2407
|
+
breaksContract,
|
|
2349
2408
|
checkSchema,
|
|
2350
2409
|
extractBlockNames,
|
|
2351
2410
|
extractBlocks,
|
|
2352
2411
|
extractModelBlocks,
|
|
2353
2412
|
extractModelNames,
|
|
2413
|
+
parseBlockAttributes,
|
|
2354
2414
|
parseEnumValues,
|
|
2355
2415
|
parseFields,
|
|
2356
2416
|
parseSchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "CLI helpers for SaaS platform consumers. Provides CliContextService (identity, MFA, production confirm, audit tag).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"qrcode-terminal": "^0.12.0",
|
|
30
|
-
"@saasicat/nest": "^0.
|
|
31
|
-
"@saasicat/spec": "^0.
|
|
32
|
-
"@saasicat/types": "^0.
|
|
30
|
+
"@saasicat/nest": "^0.12.0",
|
|
31
|
+
"@saasicat/spec": "^0.12.0",
|
|
32
|
+
"@saasicat/types": "^0.12.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@nestjs/common": "^11.0.0",
|