@saasicat/cli 0.12.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 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
 
package/dist/index.cjs CHANGED
@@ -67,6 +67,7 @@ __export(index_exports, {
67
67
  UserPortDoctorCheck: () => UserPortDoctorCheck,
68
68
  WhoAmIFlow: () => WhoAmIFlow,
69
69
  applyFragmentBlocks: () => applyFragmentBlocks,
70
+ blankStringLiterals: () => blankStringLiterals,
70
71
  blockBodyLines: () => blockBodyLines,
71
72
  breaksContract: () => breaksContract,
72
73
  checkSchema: () => checkSchema,
@@ -78,7 +79,8 @@ __export(index_exports, {
78
79
  parseEnumValues: () => parseEnumValues,
79
80
  parseFields: () => parseFields,
80
81
  parseSchema: () => parseSchema,
81
- stripLineComment: () => stripLineComment
82
+ stripLineComment: () => stripLineComment,
83
+ structuralOnly: () => structuralOnly
82
84
  });
83
85
  module.exports = __toCommonJS(index_exports);
84
86
 
@@ -1111,7 +1113,7 @@ var PLATFORM_DOCTOR_CHECK_PROVIDERS = [
1111
1113
 
1112
1114
  // src/prisma-blocks.ts
1113
1115
  function stripLineComment(line) {
1114
- const commentStart = line.indexOf("//");
1116
+ const commentStart = blankStringLiterals(line).indexOf("//");
1115
1117
  return commentStart === -1 ? line : line.slice(0, commentStart);
1116
1118
  }
1117
1119
  __name(stripLineComment, "stripLineComment");
@@ -1119,11 +1121,45 @@ function declarationPattern(keyword) {
1119
1121
  return new RegExp(`^\\s*${keyword}\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{`);
1120
1122
  }
1121
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");
1122
1158
  function extractBlockNames(schema, keyword) {
1123
1159
  const pattern = declarationPattern(keyword);
1124
1160
  const names = [];
1125
1161
  for (const line of schema.split("\n")) {
1126
- const match = stripLineComment(line).match(pattern);
1162
+ const match = structuralOnly(line).match(pattern);
1127
1163
  if (match) names.push(match[1]);
1128
1164
  }
1129
1165
  return names;
@@ -1134,7 +1170,7 @@ function extractBlocks(schema, keyword) {
1134
1170
  const blocks = /* @__PURE__ */ new Map();
1135
1171
  let current = null;
1136
1172
  for (const rawLine of schema.split("\n")) {
1137
- const stripped = stripLineComment(rawLine);
1173
+ const stripped = structuralOnly(rawLine);
1138
1174
  const openCount = (stripped.match(/\{/g) ?? []).length;
1139
1175
  const closeCount = (stripped.match(/\}/g) ?? []).length;
1140
1176
  if (!current) {
@@ -1163,7 +1199,7 @@ function blockBodyLines(block) {
1163
1199
  const bodyStart = block.indexOf("{");
1164
1200
  const bodyEnd = block.lastIndexOf("}");
1165
1201
  if (bodyStart === -1 || bodyEnd <= bodyStart) return [];
1166
- return block.slice(bodyStart + 1, bodyEnd).split("\n").map((line) => stripLineComment(line).trim()).filter((line) => line.length > 0 && !line.startsWith("@@"));
1202
+ return block.slice(bodyStart + 1, bodyEnd).split("\n").map((line) => structuralOnly(line).trim()).filter((line) => line.length > 0 && !line.startsWith("@@"));
1167
1203
  }
1168
1204
  __name(blockBodyLines, "blockBodyLines");
1169
1205
 
@@ -1248,17 +1284,18 @@ function parseEnumValues(block) {
1248
1284
  }
1249
1285
  __name(parseEnumValues, "parseEnumValues");
1250
1286
  function attributeFieldLists(block, attribute) {
1251
- const pattern = new RegExp(`@@${attribute}\\(([^)]*\\])`, "g");
1287
+ const pattern = new RegExp(`@@${attribute}\\(\\s*(\\[[^\\]]*\\])`, "g");
1252
1288
  return new Set([
1253
1289
  ...block.matchAll(pattern)
1254
1290
  ].map((match) => match[1].replace(/\s+/g, "")));
1255
1291
  }
1256
1292
  __name(attributeFieldLists, "attributeFieldLists");
1257
1293
  function parseBlockAttributes(name, block) {
1294
+ const active = block.split("\n").map(stripLineComment).join("\n");
1258
1295
  return {
1259
- indexes: attributeFieldLists(block, "index"),
1260
- uniques: attributeFieldLists(block, "unique"),
1261
- map: block.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
1296
+ indexes: attributeFieldLists(active, "index"),
1297
+ uniques: attributeFieldLists(active, "unique"),
1298
+ map: active.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
1262
1299
  };
1263
1300
  }
1264
1301
  __name(parseBlockAttributes, "parseBlockAttributes");
@@ -2485,6 +2522,7 @@ UserCommands = _ts_decorate14([
2485
2522
  UserPortDoctorCheck,
2486
2523
  WhoAmIFlow,
2487
2524
  applyFragmentBlocks,
2525
+ blankStringLiterals,
2488
2526
  blockBodyLines,
2489
2527
  breaksContract,
2490
2528
  checkSchema,
@@ -2496,5 +2534,6 @@ UserCommands = _ts_decorate14([
2496
2534
  parseEnumValues,
2497
2535
  parseFields,
2498
2536
  parseSchema,
2499
- stripLineComment
2537
+ stripLineComment,
2538
+ structuralOnly
2500
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. Deliberately index-based instead of
331
- * `replace(/\/\/.*$/, '')`: the regex backtracks quadratically on lines with
332
- * many single slashes.
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
  /**
@@ -454,6 +475,11 @@ declare function parseEnumValues(block: string): string[];
454
475
  * Parses the block-level attributes of a `model`. Comparing these is what
455
476
  * catches a missing index or unique constraint — differences the field-level
456
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.
457
483
  */
458
484
  declare function parseBlockAttributes(name: string, block: string): BlockAttributes;
459
485
  /** Parses every `model` and `enum` declaration of a Prisma schema. */
@@ -661,4 +687,4 @@ declare class UserCommands extends CommandRunner {
661
687
  parsePassword(val: string): string;
662
688
  }
663
689
 
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 };
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. Deliberately index-based instead of
331
- * `replace(/\/\/.*$/, '')`: the regex backtracks quadratically on lines with
332
- * many single slashes.
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
  /**
@@ -454,6 +475,11 @@ declare function parseEnumValues(block: string): string[];
454
475
  * Parses the block-level attributes of a `model`. Comparing these is what
455
476
  * catches a missing index or unique constraint — differences the field-level
456
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.
457
483
  */
458
484
  declare function parseBlockAttributes(name: string, block: string): BlockAttributes;
459
485
  /** Parses every `model` and `enum` declaration of a Prisma schema. */
@@ -661,4 +687,4 @@ declare class UserCommands extends CommandRunner {
661
687
  parsePassword(val: string): string;
662
688
  }
663
689
 
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 };
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 = stripLineComment(line).match(pattern);
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 = stripLineComment(rawLine);
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) => stripLineComment(line).trim()).filter((line) => line.length > 0 && !line.startsWith("@@"));
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
 
@@ -1167,17 +1201,18 @@ function parseEnumValues(block) {
1167
1201
  }
1168
1202
  __name(parseEnumValues, "parseEnumValues");
1169
1203
  function attributeFieldLists(block, attribute) {
1170
- const pattern = new RegExp(`@@${attribute}\\(([^)]*\\])`, "g");
1204
+ const pattern = new RegExp(`@@${attribute}\\(\\s*(\\[[^\\]]*\\])`, "g");
1171
1205
  return new Set([
1172
1206
  ...block.matchAll(pattern)
1173
1207
  ].map((match) => match[1].replace(/\s+/g, "")));
1174
1208
  }
1175
1209
  __name(attributeFieldLists, "attributeFieldLists");
1176
1210
  function parseBlockAttributes(name, block) {
1211
+ const active = block.split("\n").map(stripLineComment).join("\n");
1177
1212
  return {
1178
- indexes: attributeFieldLists(block, "index"),
1179
- uniques: attributeFieldLists(block, "unique"),
1180
- map: block.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
1213
+ indexes: attributeFieldLists(active, "index"),
1214
+ uniques: attributeFieldLists(active, "unique"),
1215
+ map: active.match(/@@map\("([^"]+)"\)/)?.[1] ?? name
1181
1216
  };
1182
1217
  }
1183
1218
  __name(parseBlockAttributes, "parseBlockAttributes");
@@ -2403,6 +2438,7 @@ export {
2403
2438
  UserPortDoctorCheck,
2404
2439
  WhoAmIFlow,
2405
2440
  applyFragmentBlocks,
2441
+ blankStringLiterals,
2406
2442
  blockBodyLines,
2407
2443
  breaksContract,
2408
2444
  checkSchema,
@@ -2414,5 +2450,6 @@ export {
2414
2450
  parseEnumValues,
2415
2451
  parseFields,
2416
2452
  parseSchema,
2417
- stripLineComment
2453
+ stripLineComment,
2454
+ structuralOnly
2418
2455
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasicat/cli",
3
- "version": "0.12.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.12.0",
31
- "@saasicat/spec": "^0.12.0",
32
- "@saasicat/types": "^0.12.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",