@saasicat/cli 0.11.0 → 0.12.1
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 +33 -0
- package/dist/index.cjs +108 -7
- package/dist/index.d.cts +66 -4
- package/dist/index.d.ts +66 -4
- package/dist/index.js +103 -6
- package/package.json +4 -4
package/bin/saasicat.js
CHANGED
|
@@ -53,6 +53,19 @@ function resolveFragmentsDir() {
|
|
|
53
53
|
async function selectFragmentFiles(dir, filter) {
|
|
54
54
|
const files = (await readdir(dir)).filter((f) => f.endsWith('.prisma')).sort();
|
|
55
55
|
if (!filter) return files;
|
|
56
|
+
|
|
57
|
+
// A selector that matches nothing is a typo, and silently dropping it would
|
|
58
|
+
// leave part of the requested schema surface unchecked while the command
|
|
59
|
+
// still exits 0 — the worst outcome for something used as a CI gate.
|
|
60
|
+
const available = new Set(files.map((f) => f.split('-')[0]));
|
|
61
|
+
const unknown = filter.filter((prefix) => !available.has(prefix));
|
|
62
|
+
if (unknown.length > 0) {
|
|
63
|
+
console.error(
|
|
64
|
+
`✗ Unbekannte Fragmente: ${unknown.join(', ')}. ` +
|
|
65
|
+
`Verfügbar: ${[...available].join(', ')}`,
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
56
69
|
return files.filter((f) => filter.includes(f.split('-')[0]));
|
|
57
70
|
}
|
|
58
71
|
|
|
@@ -169,6 +182,26 @@ function printCheckReport(report) {
|
|
|
169
182
|
console.log('');
|
|
170
183
|
}
|
|
171
184
|
|
|
185
|
+
const breaking = report.missingBlockAttributes.filter((a) => a.kind !== 'index');
|
|
186
|
+
if (breaking.length > 0) {
|
|
187
|
+
console.log(`✗ Fehlende Constraints (${breaking.length}):`);
|
|
188
|
+
for (const { model, kind, expected, actual } of breaking) {
|
|
189
|
+
const suffix = kind === 'map' ? ` — vorhanden: @@map("${actual}")` : '';
|
|
190
|
+
console.log(` ${model.padEnd(28)} ${expected}${suffix}`);
|
|
191
|
+
}
|
|
192
|
+
console.log('');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const missingIndexes = report.missingBlockAttributes.filter((a) => a.kind === 'index');
|
|
196
|
+
if (missingIndexes.length > 0) {
|
|
197
|
+
console.log(`→ Fehlende Indizes (${missingIndexes.length}):`);
|
|
198
|
+
for (const { model, expected } of missingIndexes) {
|
|
199
|
+
console.log(` ${model.padEnd(28)} ${expected}`);
|
|
200
|
+
}
|
|
201
|
+
console.log(' Kein Fehler — kostet Query-Zeit, bricht aber nichts.');
|
|
202
|
+
console.log('');
|
|
203
|
+
}
|
|
204
|
+
|
|
172
205
|
const absent = [...report.absentModels, ...report.absentEnums];
|
|
173
206
|
if (absent.length > 0) {
|
|
174
207
|
console.log(`→ Nicht übernommen (${absent.length}): ${absent.join(', ')}`);
|
package/dist/index.cjs
CHANGED
|
@@ -67,16 +67,20 @@ __export(index_exports, {
|
|
|
67
67
|
UserPortDoctorCheck: () => UserPortDoctorCheck,
|
|
68
68
|
WhoAmIFlow: () => WhoAmIFlow,
|
|
69
69
|
applyFragmentBlocks: () => applyFragmentBlocks,
|
|
70
|
+
blankStringLiterals: () => blankStringLiterals,
|
|
70
71
|
blockBodyLines: () => blockBodyLines,
|
|
72
|
+
breaksContract: () => breaksContract,
|
|
71
73
|
checkSchema: () => checkSchema,
|
|
72
74
|
extractBlockNames: () => extractBlockNames,
|
|
73
75
|
extractBlocks: () => extractBlocks,
|
|
74
76
|
extractModelBlocks: () => extractModelBlocks,
|
|
75
77
|
extractModelNames: () => extractModelNames,
|
|
78
|
+
parseBlockAttributes: () => parseBlockAttributes,
|
|
76
79
|
parseEnumValues: () => parseEnumValues,
|
|
77
80
|
parseFields: () => parseFields,
|
|
78
81
|
parseSchema: () => parseSchema,
|
|
79
|
-
stripLineComment: () => stripLineComment
|
|
82
|
+
stripLineComment: () => stripLineComment,
|
|
83
|
+
structuralOnly: () => structuralOnly
|
|
80
84
|
});
|
|
81
85
|
module.exports = __toCommonJS(index_exports);
|
|
82
86
|
|
|
@@ -1109,7 +1113,7 @@ var PLATFORM_DOCTOR_CHECK_PROVIDERS = [
|
|
|
1109
1113
|
|
|
1110
1114
|
// src/prisma-blocks.ts
|
|
1111
1115
|
function stripLineComment(line) {
|
|
1112
|
-
const commentStart = line.indexOf("//");
|
|
1116
|
+
const commentStart = blankStringLiterals(line).indexOf("//");
|
|
1113
1117
|
return commentStart === -1 ? line : line.slice(0, commentStart);
|
|
1114
1118
|
}
|
|
1115
1119
|
__name(stripLineComment, "stripLineComment");
|
|
@@ -1117,11 +1121,45 @@ function declarationPattern(keyword) {
|
|
|
1117
1121
|
return new RegExp(`^\\s*${keyword}\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{`);
|
|
1118
1122
|
}
|
|
1119
1123
|
__name(declarationPattern, "declarationPattern");
|
|
1124
|
+
function blankStringLiterals(line) {
|
|
1125
|
+
let out = "";
|
|
1126
|
+
let inString = false;
|
|
1127
|
+
let escaped = false;
|
|
1128
|
+
for (const char of line) {
|
|
1129
|
+
if (!inString) {
|
|
1130
|
+
out += char;
|
|
1131
|
+
if (char === '"') inString = true;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (escaped) {
|
|
1135
|
+
out += " ";
|
|
1136
|
+
escaped = false;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (char === "\\") {
|
|
1140
|
+
out += " ";
|
|
1141
|
+
escaped = true;
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
if (char === '"') {
|
|
1145
|
+
out += '"';
|
|
1146
|
+
inString = false;
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
out += " ";
|
|
1150
|
+
}
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1153
|
+
__name(blankStringLiterals, "blankStringLiterals");
|
|
1154
|
+
function structuralOnly(line) {
|
|
1155
|
+
return stripLineComment(blankStringLiterals(line));
|
|
1156
|
+
}
|
|
1157
|
+
__name(structuralOnly, "structuralOnly");
|
|
1120
1158
|
function extractBlockNames(schema, keyword) {
|
|
1121
1159
|
const pattern = declarationPattern(keyword);
|
|
1122
1160
|
const names = [];
|
|
1123
1161
|
for (const line of schema.split("\n")) {
|
|
1124
|
-
const match =
|
|
1162
|
+
const match = structuralOnly(line).match(pattern);
|
|
1125
1163
|
if (match) names.push(match[1]);
|
|
1126
1164
|
}
|
|
1127
1165
|
return names;
|
|
@@ -1132,7 +1170,7 @@ function extractBlocks(schema, keyword) {
|
|
|
1132
1170
|
const blocks = /* @__PURE__ */ new Map();
|
|
1133
1171
|
let current = null;
|
|
1134
1172
|
for (const rawLine of schema.split("\n")) {
|
|
1135
|
-
const stripped =
|
|
1173
|
+
const stripped = structuralOnly(rawLine);
|
|
1136
1174
|
const openCount = (stripped.match(/\{/g) ?? []).length;
|
|
1137
1175
|
const closeCount = (stripped.match(/\}/g) ?? []).length;
|
|
1138
1176
|
if (!current) {
|
|
@@ -1161,7 +1199,7 @@ function blockBodyLines(block) {
|
|
|
1161
1199
|
const bodyStart = block.indexOf("{");
|
|
1162
1200
|
const bodyEnd = block.lastIndexOf("}");
|
|
1163
1201
|
if (bodyStart === -1 || bodyEnd <= bodyStart) return [];
|
|
1164
|
-
return block.slice(bodyStart + 1, bodyEnd).split("\n").map((line) =>
|
|
1202
|
+
return block.slice(bodyStart + 1, bodyEnd).split("\n").map((line) => structuralOnly(line).trim()).filter((line) => line.length > 0 && !line.startsWith("@@"));
|
|
1165
1203
|
}
|
|
1166
1204
|
__name(blockBodyLines, "blockBodyLines");
|
|
1167
1205
|
|
|
@@ -1245,10 +1283,28 @@ function parseEnumValues(block) {
|
|
|
1245
1283
|
return values;
|
|
1246
1284
|
}
|
|
1247
1285
|
__name(parseEnumValues, "parseEnumValues");
|
|
1286
|
+
function attributeFieldLists(block, attribute) {
|
|
1287
|
+
const pattern = new RegExp(`@@${attribute}\\(\\s*(\\[[^\\]]*\\])`, "g");
|
|
1288
|
+
return new Set([
|
|
1289
|
+
...block.matchAll(pattern)
|
|
1290
|
+
].map((match) => match[1].replace(/\s+/g, "")));
|
|
1291
|
+
}
|
|
1292
|
+
__name(attributeFieldLists, "attributeFieldLists");
|
|
1293
|
+
function parseBlockAttributes(name, block) {
|
|
1294
|
+
const active = block.split("\n").map(stripLineComment).join("\n");
|
|
1295
|
+
return {
|
|
1296
|
+
indexes: attributeFieldLists(active, "index"),
|
|
1297
|
+
uniques: attributeFieldLists(active, "unique"),
|
|
1298
|
+
map: active.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
__name(parseBlockAttributes, "parseBlockAttributes");
|
|
1248
1302
|
function parseSchema(schema) {
|
|
1249
1303
|
const models = /* @__PURE__ */ new Map();
|
|
1304
|
+
const modelAttributes = /* @__PURE__ */ new Map();
|
|
1250
1305
|
for (const [name, block] of extractBlocks(schema, "model")) {
|
|
1251
1306
|
models.set(name, parseFields(block));
|
|
1307
|
+
modelAttributes.set(name, parseBlockAttributes(name, block));
|
|
1252
1308
|
}
|
|
1253
1309
|
const enums = /* @__PURE__ */ new Map();
|
|
1254
1310
|
for (const [name, block] of extractBlocks(schema, "enum")) {
|
|
@@ -1256,6 +1312,7 @@ function parseSchema(schema) {
|
|
|
1256
1312
|
}
|
|
1257
1313
|
return {
|
|
1258
1314
|
models,
|
|
1315
|
+
modelAttributes,
|
|
1259
1316
|
enums
|
|
1260
1317
|
};
|
|
1261
1318
|
}
|
|
@@ -1292,12 +1349,46 @@ function compareFields(model, specFields, appFields, appEnums, missingFields, fi
|
|
|
1292
1349
|
}
|
|
1293
1350
|
}
|
|
1294
1351
|
__name(compareFields, "compareFields");
|
|
1352
|
+
function compareBlockAttributes(model, spec, app, out) {
|
|
1353
|
+
if (spec.map !== app.map) {
|
|
1354
|
+
out.push({
|
|
1355
|
+
model,
|
|
1356
|
+
kind: "map",
|
|
1357
|
+
expected: `@@map("${spec.map}")`,
|
|
1358
|
+
actual: app.map
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
for (const fields of spec.uniques) {
|
|
1362
|
+
if (!app.uniques.has(fields)) {
|
|
1363
|
+
out.push({
|
|
1364
|
+
model,
|
|
1365
|
+
kind: "unique",
|
|
1366
|
+
expected: `@@unique(${fields})`
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
for (const fields of spec.indexes) {
|
|
1371
|
+
if (!app.indexes.has(fields)) {
|
|
1372
|
+
out.push({
|
|
1373
|
+
model,
|
|
1374
|
+
kind: "index",
|
|
1375
|
+
expected: `@@index(${fields})`
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
__name(compareBlockAttributes, "compareBlockAttributes");
|
|
1381
|
+
function breaksContract(attribute) {
|
|
1382
|
+
return attribute.kind !== "index";
|
|
1383
|
+
}
|
|
1384
|
+
__name(breaksContract, "breaksContract");
|
|
1295
1385
|
function checkSchema(specSchema, appSchema) {
|
|
1296
1386
|
const spec = parseSchema(specSchema);
|
|
1297
1387
|
const app = parseSchema(appSchema);
|
|
1298
1388
|
const absentModels = [];
|
|
1299
1389
|
const missingFields = [];
|
|
1300
1390
|
const fieldMismatches = [];
|
|
1391
|
+
const missingBlockAttributes = [];
|
|
1301
1392
|
for (const [model, specFields] of spec.models) {
|
|
1302
1393
|
const appFields = app.models.get(model);
|
|
1303
1394
|
if (!appFields) {
|
|
@@ -1305,6 +1396,11 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1305
1396
|
continue;
|
|
1306
1397
|
}
|
|
1307
1398
|
compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
|
|
1399
|
+
const specAttrs = spec.modelAttributes.get(model);
|
|
1400
|
+
const appAttrs = app.modelAttributes.get(model);
|
|
1401
|
+
if (specAttrs && appAttrs) {
|
|
1402
|
+
compareBlockAttributes(model, specAttrs, appAttrs, missingBlockAttributes);
|
|
1403
|
+
}
|
|
1308
1404
|
}
|
|
1309
1405
|
const absentEnums = [];
|
|
1310
1406
|
const missingEnumValues = [];
|
|
@@ -1329,9 +1425,10 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1329
1425
|
missingFields,
|
|
1330
1426
|
missingEnumValues,
|
|
1331
1427
|
fieldMismatches,
|
|
1428
|
+
missingBlockAttributes,
|
|
1332
1429
|
checkedModelCount: spec.models.size - absentModels.length,
|
|
1333
1430
|
checkedEnumCount: spec.enums.size - absentEnums.length,
|
|
1334
|
-
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0
|
|
1431
|
+
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0 && !missingBlockAttributes.some(breaksContract)
|
|
1335
1432
|
};
|
|
1336
1433
|
}
|
|
1337
1434
|
__name(checkSchema, "checkSchema");
|
|
@@ -2425,14 +2522,18 @@ UserCommands = _ts_decorate14([
|
|
|
2425
2522
|
UserPortDoctorCheck,
|
|
2426
2523
|
WhoAmIFlow,
|
|
2427
2524
|
applyFragmentBlocks,
|
|
2525
|
+
blankStringLiterals,
|
|
2428
2526
|
blockBodyLines,
|
|
2527
|
+
breaksContract,
|
|
2429
2528
|
checkSchema,
|
|
2430
2529
|
extractBlockNames,
|
|
2431
2530
|
extractBlocks,
|
|
2432
2531
|
extractModelBlocks,
|
|
2433
2532
|
extractModelNames,
|
|
2533
|
+
parseBlockAttributes,
|
|
2434
2534
|
parseEnumValues,
|
|
2435
2535
|
parseFields,
|
|
2436
2536
|
parseSchema,
|
|
2437
|
-
stripLineComment
|
|
2537
|
+
stripLineComment,
|
|
2538
|
+
structuralOnly
|
|
2438
2539
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -327,11 +327,32 @@ declare class AdminManifestDoctorCheck implements DoctorCheck {
|
|
|
327
327
|
declare const PLATFORM_DOCTOR_CHECK_PROVIDERS: Array<Type<DoctorCheck>>;
|
|
328
328
|
|
|
329
329
|
/**
|
|
330
|
-
* Cuts off a trailing `//` comment
|
|
331
|
-
*
|
|
332
|
-
*
|
|
330
|
+
* Cuts off a trailing `//` comment, ignoring `//` inside string literals — a
|
|
331
|
+
* `@default("http://x")` must survive intact. The position is found on a
|
|
332
|
+
* string-blanked copy, but the original line is what gets cut, so quoted
|
|
333
|
+
* content is preserved.
|
|
334
|
+
*
|
|
335
|
+
* Deliberately index-based instead of `replace(/\/\/.*$/, '')`: the regex
|
|
336
|
+
* backtracks quadratically on lines with many single slashes.
|
|
333
337
|
*/
|
|
334
338
|
declare function stripLineComment(line: string): string;
|
|
339
|
+
/**
|
|
340
|
+
* Blanks the contents of double-quoted strings, keeping the quotes and the
|
|
341
|
+
* line length so column positions stay put. Brace counting must not see
|
|
342
|
+
* `@default("}")` as the end of a model — it would close the block early and
|
|
343
|
+
* every field below it would look missing.
|
|
344
|
+
*
|
|
345
|
+
* A linear scan rather than `"(?:[^"\\]|\\.)*"`: that pattern backtracks
|
|
346
|
+
* quadratically on many repeated `\"` (CodeQL `js/polynomial-redos`), and this
|
|
347
|
+
* runs over every line of a consumer schema.
|
|
348
|
+
*/
|
|
349
|
+
declare function blankStringLiterals(line: string): string;
|
|
350
|
+
/**
|
|
351
|
+
* Comment- and string-safe view of a line, for structural scanning. Strings are
|
|
352
|
+
* blanked first: `@default("http://x")` contains `//`, and stripping comments
|
|
353
|
+
* before that would cut the line in half.
|
|
354
|
+
*/
|
|
355
|
+
declare function structuralOnly(line: string): string;
|
|
335
356
|
/** Returns the names of all top-level `<keyword> X { … }` blocks. */
|
|
336
357
|
declare function extractBlockNames(schema: string, keyword: string): string[];
|
|
337
358
|
/**
|
|
@@ -383,8 +404,17 @@ interface FieldSignature {
|
|
|
383
404
|
optional: boolean;
|
|
384
405
|
list: boolean;
|
|
385
406
|
}
|
|
407
|
+
interface BlockAttributes {
|
|
408
|
+
/** Field lists of `@@index`, normalised to `[a,b]` — attribute options dropped. */
|
|
409
|
+
indexes: Set<string>;
|
|
410
|
+
/** Same for `@@unique`. */
|
|
411
|
+
uniques: Set<string>;
|
|
412
|
+
/** `@@map` target, or the model name when unmapped. */
|
|
413
|
+
map: string;
|
|
414
|
+
}
|
|
386
415
|
interface ParsedSchema {
|
|
387
416
|
models: Map<string, Map<string, FieldSignature>>;
|
|
417
|
+
modelAttributes: Map<string, BlockAttributes>;
|
|
388
418
|
enums: Map<string, string[]>;
|
|
389
419
|
}
|
|
390
420
|
interface MissingField {
|
|
@@ -405,6 +435,15 @@ interface FieldMismatch {
|
|
|
405
435
|
expected: string;
|
|
406
436
|
actual: string;
|
|
407
437
|
}
|
|
438
|
+
type BlockAttributeKind = 'index' | 'unique' | 'map';
|
|
439
|
+
interface MissingBlockAttribute {
|
|
440
|
+
model: string;
|
|
441
|
+
kind: BlockAttributeKind;
|
|
442
|
+
/** Rendered attribute, e.g. `@@index([planId, validFrom])`. */
|
|
443
|
+
expected: string;
|
|
444
|
+
/** For `map`: what the consumer maps to instead. */
|
|
445
|
+
actual?: string;
|
|
446
|
+
}
|
|
408
447
|
interface SchemaCheckReport {
|
|
409
448
|
/** Platform models the consumer does not carry — informational. */
|
|
410
449
|
absentModels: string[];
|
|
@@ -413,6 +452,11 @@ interface SchemaCheckReport {
|
|
|
413
452
|
missingFields: MissingField[];
|
|
414
453
|
missingEnumValues: MissingEnumValue[];
|
|
415
454
|
fieldMismatches: FieldMismatch[];
|
|
455
|
+
/**
|
|
456
|
+
* Block-level attributes the spec declares and the consumer lacks:
|
|
457
|
+
* `@@index`, `@@unique`, and a diverging `@@map`.
|
|
458
|
+
*/
|
|
459
|
+
missingBlockAttributes: MissingBlockAttribute[];
|
|
416
460
|
/** Models present in both schemas, i.e. actually compared. */
|
|
417
461
|
checkedModelCount: number;
|
|
418
462
|
/** Enums present in both schemas, i.e. actually compared. */
|
|
@@ -427,8 +471,26 @@ declare function parseFields(block: string): Map<string, FieldSignature>;
|
|
|
427
471
|
* (`enum Role { ADMIN USER }`); a trailing `@map(…)` attribute is not a value.
|
|
428
472
|
*/
|
|
429
473
|
declare function parseEnumValues(block: string): string[];
|
|
474
|
+
/**
|
|
475
|
+
* Parses the block-level attributes of a `model`. Comparing these is what
|
|
476
|
+
* catches a missing index or unique constraint — differences the field-level
|
|
477
|
+
* comparison is blind to.
|
|
478
|
+
*
|
|
479
|
+
* The block is reduced to its structural content first. A consumer who
|
|
480
|
+
* comments out `// @@unique([tenantId])` has removed the constraint, and
|
|
481
|
+
* reading the raw text would count it as present — passing the very check that
|
|
482
|
+
* exists to catch its absence.
|
|
483
|
+
*/
|
|
484
|
+
declare function parseBlockAttributes(name: string, block: string): BlockAttributes;
|
|
430
485
|
/** Parses every `model` and `enum` declaration of a Prisma schema. */
|
|
431
486
|
declare function parseSchema(schema: string): ParsedSchema;
|
|
487
|
+
/**
|
|
488
|
+
* A missing index costs query time; a missing `@@unique` or a diverging
|
|
489
|
+
* `@@map` breaks correctness — the platform relies on the constraint holding,
|
|
490
|
+
* and on finding the table under its canonical name. Only the latter two fail
|
|
491
|
+
* the check.
|
|
492
|
+
*/
|
|
493
|
+
declare function breaksContract(attribute: MissingBlockAttribute): boolean;
|
|
432
494
|
/**
|
|
433
495
|
* Compares a consumer schema against the canonical fragments. `specSchema` is
|
|
434
496
|
* the concatenation of the fragments the check should cover.
|
|
@@ -625,4 +687,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
625
687
|
parsePassword(val: string): string;
|
|
626
688
|
}
|
|
627
689
|
|
|
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 };
|
|
690
|
+
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, blankStringLiterals, blockBodyLines, breaksContract, checkSchema, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, parseBlockAttributes, parseEnumValues, parseFields, parseSchema, stripLineComment, structuralOnly };
|
package/dist/index.d.ts
CHANGED
|
@@ -327,11 +327,32 @@ declare class AdminManifestDoctorCheck implements DoctorCheck {
|
|
|
327
327
|
declare const PLATFORM_DOCTOR_CHECK_PROVIDERS: Array<Type<DoctorCheck>>;
|
|
328
328
|
|
|
329
329
|
/**
|
|
330
|
-
* Cuts off a trailing `//` comment
|
|
331
|
-
*
|
|
332
|
-
*
|
|
330
|
+
* Cuts off a trailing `//` comment, ignoring `//` inside string literals — a
|
|
331
|
+
* `@default("http://x")` must survive intact. The position is found on a
|
|
332
|
+
* string-blanked copy, but the original line is what gets cut, so quoted
|
|
333
|
+
* content is preserved.
|
|
334
|
+
*
|
|
335
|
+
* Deliberately index-based instead of `replace(/\/\/.*$/, '')`: the regex
|
|
336
|
+
* backtracks quadratically on lines with many single slashes.
|
|
333
337
|
*/
|
|
334
338
|
declare function stripLineComment(line: string): string;
|
|
339
|
+
/**
|
|
340
|
+
* Blanks the contents of double-quoted strings, keeping the quotes and the
|
|
341
|
+
* line length so column positions stay put. Brace counting must not see
|
|
342
|
+
* `@default("}")` as the end of a model — it would close the block early and
|
|
343
|
+
* every field below it would look missing.
|
|
344
|
+
*
|
|
345
|
+
* A linear scan rather than `"(?:[^"\\]|\\.)*"`: that pattern backtracks
|
|
346
|
+
* quadratically on many repeated `\"` (CodeQL `js/polynomial-redos`), and this
|
|
347
|
+
* runs over every line of a consumer schema.
|
|
348
|
+
*/
|
|
349
|
+
declare function blankStringLiterals(line: string): string;
|
|
350
|
+
/**
|
|
351
|
+
* Comment- and string-safe view of a line, for structural scanning. Strings are
|
|
352
|
+
* blanked first: `@default("http://x")` contains `//`, and stripping comments
|
|
353
|
+
* before that would cut the line in half.
|
|
354
|
+
*/
|
|
355
|
+
declare function structuralOnly(line: string): string;
|
|
335
356
|
/** Returns the names of all top-level `<keyword> X { … }` blocks. */
|
|
336
357
|
declare function extractBlockNames(schema: string, keyword: string): string[];
|
|
337
358
|
/**
|
|
@@ -383,8 +404,17 @@ interface FieldSignature {
|
|
|
383
404
|
optional: boolean;
|
|
384
405
|
list: boolean;
|
|
385
406
|
}
|
|
407
|
+
interface BlockAttributes {
|
|
408
|
+
/** Field lists of `@@index`, normalised to `[a,b]` — attribute options dropped. */
|
|
409
|
+
indexes: Set<string>;
|
|
410
|
+
/** Same for `@@unique`. */
|
|
411
|
+
uniques: Set<string>;
|
|
412
|
+
/** `@@map` target, or the model name when unmapped. */
|
|
413
|
+
map: string;
|
|
414
|
+
}
|
|
386
415
|
interface ParsedSchema {
|
|
387
416
|
models: Map<string, Map<string, FieldSignature>>;
|
|
417
|
+
modelAttributes: Map<string, BlockAttributes>;
|
|
388
418
|
enums: Map<string, string[]>;
|
|
389
419
|
}
|
|
390
420
|
interface MissingField {
|
|
@@ -405,6 +435,15 @@ interface FieldMismatch {
|
|
|
405
435
|
expected: string;
|
|
406
436
|
actual: string;
|
|
407
437
|
}
|
|
438
|
+
type BlockAttributeKind = 'index' | 'unique' | 'map';
|
|
439
|
+
interface MissingBlockAttribute {
|
|
440
|
+
model: string;
|
|
441
|
+
kind: BlockAttributeKind;
|
|
442
|
+
/** Rendered attribute, e.g. `@@index([planId, validFrom])`. */
|
|
443
|
+
expected: string;
|
|
444
|
+
/** For `map`: what the consumer maps to instead. */
|
|
445
|
+
actual?: string;
|
|
446
|
+
}
|
|
408
447
|
interface SchemaCheckReport {
|
|
409
448
|
/** Platform models the consumer does not carry — informational. */
|
|
410
449
|
absentModels: string[];
|
|
@@ -413,6 +452,11 @@ interface SchemaCheckReport {
|
|
|
413
452
|
missingFields: MissingField[];
|
|
414
453
|
missingEnumValues: MissingEnumValue[];
|
|
415
454
|
fieldMismatches: FieldMismatch[];
|
|
455
|
+
/**
|
|
456
|
+
* Block-level attributes the spec declares and the consumer lacks:
|
|
457
|
+
* `@@index`, `@@unique`, and a diverging `@@map`.
|
|
458
|
+
*/
|
|
459
|
+
missingBlockAttributes: MissingBlockAttribute[];
|
|
416
460
|
/** Models present in both schemas, i.e. actually compared. */
|
|
417
461
|
checkedModelCount: number;
|
|
418
462
|
/** Enums present in both schemas, i.e. actually compared. */
|
|
@@ -427,8 +471,26 @@ declare function parseFields(block: string): Map<string, FieldSignature>;
|
|
|
427
471
|
* (`enum Role { ADMIN USER }`); a trailing `@map(…)` attribute is not a value.
|
|
428
472
|
*/
|
|
429
473
|
declare function parseEnumValues(block: string): string[];
|
|
474
|
+
/**
|
|
475
|
+
* Parses the block-level attributes of a `model`. Comparing these is what
|
|
476
|
+
* catches a missing index or unique constraint — differences the field-level
|
|
477
|
+
* comparison is blind to.
|
|
478
|
+
*
|
|
479
|
+
* The block is reduced to its structural content first. A consumer who
|
|
480
|
+
* comments out `// @@unique([tenantId])` has removed the constraint, and
|
|
481
|
+
* reading the raw text would count it as present — passing the very check that
|
|
482
|
+
* exists to catch its absence.
|
|
483
|
+
*/
|
|
484
|
+
declare function parseBlockAttributes(name: string, block: string): BlockAttributes;
|
|
430
485
|
/** Parses every `model` and `enum` declaration of a Prisma schema. */
|
|
431
486
|
declare function parseSchema(schema: string): ParsedSchema;
|
|
487
|
+
/**
|
|
488
|
+
* A missing index costs query time; a missing `@@unique` or a diverging
|
|
489
|
+
* `@@map` breaks correctness — the platform relies on the constraint holding,
|
|
490
|
+
* and on finding the table under its canonical name. Only the latter two fail
|
|
491
|
+
* the check.
|
|
492
|
+
*/
|
|
493
|
+
declare function breaksContract(attribute: MissingBlockAttribute): boolean;
|
|
432
494
|
/**
|
|
433
495
|
* Compares a consumer schema against the canonical fragments. `specSchema` is
|
|
434
496
|
* the concatenation of the fragments the check should cover.
|
|
@@ -625,4 +687,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
625
687
|
parsePassword(val: string): string;
|
|
626
688
|
}
|
|
627
689
|
|
|
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 };
|
|
690
|
+
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, blankStringLiterals, blockBodyLines, breaksContract, checkSchema, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, parseBlockAttributes, parseEnumValues, parseFields, parseSchema, stripLineComment, structuralOnly };
|
package/dist/index.js
CHANGED
|
@@ -1030,7 +1030,7 @@ var PLATFORM_DOCTOR_CHECK_PROVIDERS = [
|
|
|
1030
1030
|
|
|
1031
1031
|
// src/prisma-blocks.ts
|
|
1032
1032
|
function stripLineComment(line) {
|
|
1033
|
-
const commentStart = line.indexOf("//");
|
|
1033
|
+
const commentStart = blankStringLiterals(line).indexOf("//");
|
|
1034
1034
|
return commentStart === -1 ? line : line.slice(0, commentStart);
|
|
1035
1035
|
}
|
|
1036
1036
|
__name(stripLineComment, "stripLineComment");
|
|
@@ -1038,11 +1038,45 @@ function declarationPattern(keyword) {
|
|
|
1038
1038
|
return new RegExp(`^\\s*${keyword}\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{`);
|
|
1039
1039
|
}
|
|
1040
1040
|
__name(declarationPattern, "declarationPattern");
|
|
1041
|
+
function blankStringLiterals(line) {
|
|
1042
|
+
let out = "";
|
|
1043
|
+
let inString = false;
|
|
1044
|
+
let escaped = false;
|
|
1045
|
+
for (const char of line) {
|
|
1046
|
+
if (!inString) {
|
|
1047
|
+
out += char;
|
|
1048
|
+
if (char === '"') inString = true;
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (escaped) {
|
|
1052
|
+
out += " ";
|
|
1053
|
+
escaped = false;
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (char === "\\") {
|
|
1057
|
+
out += " ";
|
|
1058
|
+
escaped = true;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (char === '"') {
|
|
1062
|
+
out += '"';
|
|
1063
|
+
inString = false;
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
out += " ";
|
|
1067
|
+
}
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
__name(blankStringLiterals, "blankStringLiterals");
|
|
1071
|
+
function structuralOnly(line) {
|
|
1072
|
+
return stripLineComment(blankStringLiterals(line));
|
|
1073
|
+
}
|
|
1074
|
+
__name(structuralOnly, "structuralOnly");
|
|
1041
1075
|
function extractBlockNames(schema, keyword) {
|
|
1042
1076
|
const pattern = declarationPattern(keyword);
|
|
1043
1077
|
const names = [];
|
|
1044
1078
|
for (const line of schema.split("\n")) {
|
|
1045
|
-
const match =
|
|
1079
|
+
const match = structuralOnly(line).match(pattern);
|
|
1046
1080
|
if (match) names.push(match[1]);
|
|
1047
1081
|
}
|
|
1048
1082
|
return names;
|
|
@@ -1053,7 +1087,7 @@ function extractBlocks(schema, keyword) {
|
|
|
1053
1087
|
const blocks = /* @__PURE__ */ new Map();
|
|
1054
1088
|
let current = null;
|
|
1055
1089
|
for (const rawLine of schema.split("\n")) {
|
|
1056
|
-
const stripped =
|
|
1090
|
+
const stripped = structuralOnly(rawLine);
|
|
1057
1091
|
const openCount = (stripped.match(/\{/g) ?? []).length;
|
|
1058
1092
|
const closeCount = (stripped.match(/\}/g) ?? []).length;
|
|
1059
1093
|
if (!current) {
|
|
@@ -1082,7 +1116,7 @@ function blockBodyLines(block) {
|
|
|
1082
1116
|
const bodyStart = block.indexOf("{");
|
|
1083
1117
|
const bodyEnd = block.lastIndexOf("}");
|
|
1084
1118
|
if (bodyStart === -1 || bodyEnd <= bodyStart) return [];
|
|
1085
|
-
return block.slice(bodyStart + 1, bodyEnd).split("\n").map((line) =>
|
|
1119
|
+
return block.slice(bodyStart + 1, bodyEnd).split("\n").map((line) => structuralOnly(line).trim()).filter((line) => line.length > 0 && !line.startsWith("@@"));
|
|
1086
1120
|
}
|
|
1087
1121
|
__name(blockBodyLines, "blockBodyLines");
|
|
1088
1122
|
|
|
@@ -1166,10 +1200,28 @@ function parseEnumValues(block) {
|
|
|
1166
1200
|
return values;
|
|
1167
1201
|
}
|
|
1168
1202
|
__name(parseEnumValues, "parseEnumValues");
|
|
1203
|
+
function attributeFieldLists(block, attribute) {
|
|
1204
|
+
const pattern = new RegExp(`@@${attribute}\\(\\s*(\\[[^\\]]*\\])`, "g");
|
|
1205
|
+
return new Set([
|
|
1206
|
+
...block.matchAll(pattern)
|
|
1207
|
+
].map((match) => match[1].replace(/\s+/g, "")));
|
|
1208
|
+
}
|
|
1209
|
+
__name(attributeFieldLists, "attributeFieldLists");
|
|
1210
|
+
function parseBlockAttributes(name, block) {
|
|
1211
|
+
const active = block.split("\n").map(stripLineComment).join("\n");
|
|
1212
|
+
return {
|
|
1213
|
+
indexes: attributeFieldLists(active, "index"),
|
|
1214
|
+
uniques: attributeFieldLists(active, "unique"),
|
|
1215
|
+
map: active.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
__name(parseBlockAttributes, "parseBlockAttributes");
|
|
1169
1219
|
function parseSchema(schema) {
|
|
1170
1220
|
const models = /* @__PURE__ */ new Map();
|
|
1221
|
+
const modelAttributes = /* @__PURE__ */ new Map();
|
|
1171
1222
|
for (const [name, block] of extractBlocks(schema, "model")) {
|
|
1172
1223
|
models.set(name, parseFields(block));
|
|
1224
|
+
modelAttributes.set(name, parseBlockAttributes(name, block));
|
|
1173
1225
|
}
|
|
1174
1226
|
const enums = /* @__PURE__ */ new Map();
|
|
1175
1227
|
for (const [name, block] of extractBlocks(schema, "enum")) {
|
|
@@ -1177,6 +1229,7 @@ function parseSchema(schema) {
|
|
|
1177
1229
|
}
|
|
1178
1230
|
return {
|
|
1179
1231
|
models,
|
|
1232
|
+
modelAttributes,
|
|
1180
1233
|
enums
|
|
1181
1234
|
};
|
|
1182
1235
|
}
|
|
@@ -1213,12 +1266,46 @@ function compareFields(model, specFields, appFields, appEnums, missingFields, fi
|
|
|
1213
1266
|
}
|
|
1214
1267
|
}
|
|
1215
1268
|
__name(compareFields, "compareFields");
|
|
1269
|
+
function compareBlockAttributes(model, spec, app, out) {
|
|
1270
|
+
if (spec.map !== app.map) {
|
|
1271
|
+
out.push({
|
|
1272
|
+
model,
|
|
1273
|
+
kind: "map",
|
|
1274
|
+
expected: `@@map("${spec.map}")`,
|
|
1275
|
+
actual: app.map
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
for (const fields of spec.uniques) {
|
|
1279
|
+
if (!app.uniques.has(fields)) {
|
|
1280
|
+
out.push({
|
|
1281
|
+
model,
|
|
1282
|
+
kind: "unique",
|
|
1283
|
+
expected: `@@unique(${fields})`
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
for (const fields of spec.indexes) {
|
|
1288
|
+
if (!app.indexes.has(fields)) {
|
|
1289
|
+
out.push({
|
|
1290
|
+
model,
|
|
1291
|
+
kind: "index",
|
|
1292
|
+
expected: `@@index(${fields})`
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
__name(compareBlockAttributes, "compareBlockAttributes");
|
|
1298
|
+
function breaksContract(attribute) {
|
|
1299
|
+
return attribute.kind !== "index";
|
|
1300
|
+
}
|
|
1301
|
+
__name(breaksContract, "breaksContract");
|
|
1216
1302
|
function checkSchema(specSchema, appSchema) {
|
|
1217
1303
|
const spec = parseSchema(specSchema);
|
|
1218
1304
|
const app = parseSchema(appSchema);
|
|
1219
1305
|
const absentModels = [];
|
|
1220
1306
|
const missingFields = [];
|
|
1221
1307
|
const fieldMismatches = [];
|
|
1308
|
+
const missingBlockAttributes = [];
|
|
1222
1309
|
for (const [model, specFields] of spec.models) {
|
|
1223
1310
|
const appFields = app.models.get(model);
|
|
1224
1311
|
if (!appFields) {
|
|
@@ -1226,6 +1313,11 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1226
1313
|
continue;
|
|
1227
1314
|
}
|
|
1228
1315
|
compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
|
|
1316
|
+
const specAttrs = spec.modelAttributes.get(model);
|
|
1317
|
+
const appAttrs = app.modelAttributes.get(model);
|
|
1318
|
+
if (specAttrs && appAttrs) {
|
|
1319
|
+
compareBlockAttributes(model, specAttrs, appAttrs, missingBlockAttributes);
|
|
1320
|
+
}
|
|
1229
1321
|
}
|
|
1230
1322
|
const absentEnums = [];
|
|
1231
1323
|
const missingEnumValues = [];
|
|
@@ -1250,9 +1342,10 @@ function checkSchema(specSchema, appSchema) {
|
|
|
1250
1342
|
missingFields,
|
|
1251
1343
|
missingEnumValues,
|
|
1252
1344
|
fieldMismatches,
|
|
1345
|
+
missingBlockAttributes,
|
|
1253
1346
|
checkedModelCount: spec.models.size - absentModels.length,
|
|
1254
1347
|
checkedEnumCount: spec.enums.size - absentEnums.length,
|
|
1255
|
-
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0
|
|
1348
|
+
ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0 && !missingBlockAttributes.some(breaksContract)
|
|
1256
1349
|
};
|
|
1257
1350
|
}
|
|
1258
1351
|
__name(checkSchema, "checkSchema");
|
|
@@ -2345,14 +2438,18 @@ export {
|
|
|
2345
2438
|
UserPortDoctorCheck,
|
|
2346
2439
|
WhoAmIFlow,
|
|
2347
2440
|
applyFragmentBlocks,
|
|
2441
|
+
blankStringLiterals,
|
|
2348
2442
|
blockBodyLines,
|
|
2443
|
+
breaksContract,
|
|
2349
2444
|
checkSchema,
|
|
2350
2445
|
extractBlockNames,
|
|
2351
2446
|
extractBlocks,
|
|
2352
2447
|
extractModelBlocks,
|
|
2353
2448
|
extractModelNames,
|
|
2449
|
+
parseBlockAttributes,
|
|
2354
2450
|
parseEnumValues,
|
|
2355
2451
|
parseFields,
|
|
2356
2452
|
parseSchema,
|
|
2357
|
-
stripLineComment
|
|
2453
|
+
stripLineComment,
|
|
2454
|
+
structuralOnly
|
|
2358
2455
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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.1",
|
|
31
|
+
"@saasicat/spec": "^0.12.1",
|
|
32
|
+
"@saasicat/types": "^0.12.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@nestjs/common": "^11.0.0",
|