@saasicat/cli 1.0.0-rc.19 → 1.0.0-rc.20

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
@@ -320,7 +320,16 @@ function printCheckReport(report) {
320
320
  console.log('');
321
321
  }
322
322
 
323
- const absent = [...report.absentModels, ...report.absentEnums];
323
+ // An enum a reported field names is not a fragment the app went without: the
324
+ // field above it is the drift, and copying the enum is what fixes both.
325
+ // Listing it as "not an error" in the same run would print the contradiction
326
+ // this check exists to avoid — which is exactly what it used to do for a
327
+ // relation pointing at an unadopted model.
328
+ const named = new Set(report.missingFields.map((field) => field.type));
329
+ const absent = [
330
+ ...report.absentModels,
331
+ ...report.absentEnums.filter((name) => !named.has(name)),
332
+ ];
324
333
  if (absent.length > 0) {
325
334
  console.log(`→ Not adopted (${absent.length}): ${absent.join(', ')}`);
326
335
  console.log(' Not an error — the app does not use these fragments.');
@@ -347,7 +356,21 @@ async function cmdSchemaCheck(args) {
347
356
  console.log(`→ Checking ${schemaPath} against ${files.length} fragment(s) from @saasicat/spec`);
348
357
  console.log('');
349
358
 
350
- const report = checkSchema(fragments.join('\n'), schema);
359
+ // Every model the shipped fragments declare, not only the selected ones: a
360
+ // relation from a selected fragment can point at a model in one that was
361
+ // left out, and without this the narrowed run would report that field as
362
+ // missing — the very contradiction the full run stopped producing. Without
363
+ // a filter the files in hand are already all of them.
364
+ const allFragments = filter
365
+ ? await Promise.all(
366
+ (await selectFragmentFiles(fragmentsDir, null)).map((file) =>
367
+ readFile(join(fragmentsDir, file), 'utf8'),
368
+ ),
369
+ )
370
+ : fragments;
371
+ const knownModels = new Set(extractModelNames(allFragments.join('\n')));
372
+
373
+ const report = checkSchema(fragments.join('\n'), schema, knownModels);
351
374
  printCheckReport(report);
352
375
 
353
376
  const checked = `${report.checkedModelCount} Model(s), ${report.checkedEnumCount} Enum(s)`;
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- 6d26748d12c2cc8e839f2a13b89df38079e8253fb11933454d69ef20cc6de850
1
+ 89cdc61db4ad8f0d8bc18287c0d1fe76e1a7e9410f0797a525db4c9b1bbdc3ab
package/dist/index.cjs CHANGED
@@ -1593,10 +1593,11 @@ function isDocumentedTypeSubstitution(spec, app, appEnums) {
1593
1593
  return spec.type === "String" && appEnums.has(app.type);
1594
1594
  }
1595
1595
  __name(isDocumentedTypeSubstitution, "isDocumentedTypeSubstitution");
1596
- function compareFields(model, specFields, appFields, appEnums, missingFields, fieldMismatches) {
1596
+ function compareFields(model, specFields, appFields, appEnums, notAdopted, missingFields, fieldMismatches) {
1597
1597
  for (const [name, spec] of specFields) {
1598
1598
  const app = appFields.get(name);
1599
1599
  if (!app) {
1600
+ if (notAdopted.has(spec.type)) continue;
1600
1601
  missingFields.push({
1601
1602
  model,
1602
1603
  field: name,
@@ -1675,20 +1676,26 @@ function cascadesFromTenant(model, block) {
1675
1676
  return found;
1676
1677
  }
1677
1678
  __name(cascadesFromTenant, "cascadesFromTenant");
1678
- function checkSchema(specSchema, appSchema) {
1679
+ function checkSchema(specSchema, appSchema, knownModels) {
1679
1680
  const spec = parseSchema(specSchema);
1680
1681
  const app = parseSchema(appSchema);
1681
- const absentModels = [];
1682
1682
  const missingFields = [];
1683
1683
  const fieldMismatches = [];
1684
1684
  const missingBlockAttributes = [];
1685
+ const absentModels = [
1686
+ ...spec.models.keys()
1687
+ ].filter((name) => !app.models.has(name));
1688
+ const absentEnums = [
1689
+ ...spec.enums.keys()
1690
+ ].filter((name) => !app.enums.has(name));
1691
+ const shipped = knownModels ?? new Set(spec.models.keys());
1692
+ const notAdopted = new Set([
1693
+ ...shipped
1694
+ ].filter((name) => !app.models.has(name)));
1685
1695
  for (const [model, specFields] of spec.models) {
1686
1696
  const appFields = app.models.get(model);
1687
- if (!appFields) {
1688
- absentModels.push(model);
1689
- continue;
1690
- }
1691
- compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
1697
+ if (!appFields) continue;
1698
+ compareFields(model, specFields, appFields, app.enums, notAdopted, missingFields, fieldMismatches);
1692
1699
  const specAttrs = spec.modelAttributes.get(model);
1693
1700
  const appAttrs = app.modelAttributes.get(model);
1694
1701
  if (specAttrs && appAttrs) {
@@ -1700,14 +1707,10 @@ function checkSchema(specSchema, appSchema) {
1700
1707
  const block = appBlocks.get(model);
1701
1708
  return block ? cascadesFromTenant(model, block) : [];
1702
1709
  });
1703
- const absentEnums = [];
1704
1710
  const missingEnumValues = [];
1705
1711
  for (const [name, specValues] of spec.enums) {
1706
1712
  const appValues = app.enums.get(name);
1707
- if (!appValues) {
1708
- absentEnums.push(name);
1709
- continue;
1710
- }
1713
+ if (!appValues) continue;
1711
1714
  for (const value of specValues) {
1712
1715
  if (!appValues.includes(value)) {
1713
1716
  missingEnumValues.push({
package/dist/index.d.cts CHANGED
@@ -562,8 +562,12 @@ declare function modelsKeptPastTheTenant(specSchema: string): string[];
562
562
  /**
563
563
  * Compares a consumer schema against the canonical fragments. `specSchema` is
564
564
  * the concatenation of the fragments the check should cover.
565
+ *
566
+ * @param knownModels Every model the shipped fragments declare, where the spec
567
+ * passed in is a narrowed selection of them (`schema check --fragments=…`).
568
+ * Omitted, the spec is taken to be the whole of it.
565
569
  */
566
- declare function checkSchema(specSchema: string, appSchema: string): SchemaCheckReport;
570
+ declare function checkSchema(specSchema: string, appSchema: string, knownModels?: ReadonlySet<string>): SchemaCheckReport;
567
571
 
568
572
  /** Marks the block this tool appends, and lets it recognise its own work. */
569
573
  declare const CONSTRAINTS_MARKER = "-- saasicat:constraints";
package/dist/index.d.ts CHANGED
@@ -562,8 +562,12 @@ declare function modelsKeptPastTheTenant(specSchema: string): string[];
562
562
  /**
563
563
  * Compares a consumer schema against the canonical fragments. `specSchema` is
564
564
  * the concatenation of the fragments the check should cover.
565
+ *
566
+ * @param knownModels Every model the shipped fragments declare, where the spec
567
+ * passed in is a narrowed selection of them (`schema check --fragments=…`).
568
+ * Omitted, the spec is taken to be the whole of it.
565
569
  */
566
- declare function checkSchema(specSchema: string, appSchema: string): SchemaCheckReport;
570
+ declare function checkSchema(specSchema: string, appSchema: string, knownModels?: ReadonlySet<string>): SchemaCheckReport;
567
571
 
568
572
  /** Marks the block this tool appends, and lets it recognise its own work. */
569
573
  declare const CONSTRAINTS_MARKER = "-- saasicat:constraints";
package/dist/index.js CHANGED
@@ -1457,10 +1457,11 @@ function isDocumentedTypeSubstitution(spec, app, appEnums) {
1457
1457
  return spec.type === "String" && appEnums.has(app.type);
1458
1458
  }
1459
1459
  __name(isDocumentedTypeSubstitution, "isDocumentedTypeSubstitution");
1460
- function compareFields(model, specFields, appFields, appEnums, missingFields, fieldMismatches) {
1460
+ function compareFields(model, specFields, appFields, appEnums, notAdopted, missingFields, fieldMismatches) {
1461
1461
  for (const [name, spec] of specFields) {
1462
1462
  const app = appFields.get(name);
1463
1463
  if (!app) {
1464
+ if (notAdopted.has(spec.type)) continue;
1464
1465
  missingFields.push({
1465
1466
  model,
1466
1467
  field: name,
@@ -1539,20 +1540,26 @@ function cascadesFromTenant(model, block) {
1539
1540
  return found;
1540
1541
  }
1541
1542
  __name(cascadesFromTenant, "cascadesFromTenant");
1542
- function checkSchema(specSchema, appSchema) {
1543
+ function checkSchema(specSchema, appSchema, knownModels) {
1543
1544
  const spec = parseSchema(specSchema);
1544
1545
  const app = parseSchema(appSchema);
1545
- const absentModels = [];
1546
1546
  const missingFields = [];
1547
1547
  const fieldMismatches = [];
1548
1548
  const missingBlockAttributes = [];
1549
+ const absentModels = [
1550
+ ...spec.models.keys()
1551
+ ].filter((name) => !app.models.has(name));
1552
+ const absentEnums = [
1553
+ ...spec.enums.keys()
1554
+ ].filter((name) => !app.enums.has(name));
1555
+ const shipped = knownModels ?? new Set(spec.models.keys());
1556
+ const notAdopted = new Set([
1557
+ ...shipped
1558
+ ].filter((name) => !app.models.has(name)));
1549
1559
  for (const [model, specFields] of spec.models) {
1550
1560
  const appFields = app.models.get(model);
1551
- if (!appFields) {
1552
- absentModels.push(model);
1553
- continue;
1554
- }
1555
- compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
1561
+ if (!appFields) continue;
1562
+ compareFields(model, specFields, appFields, app.enums, notAdopted, missingFields, fieldMismatches);
1556
1563
  const specAttrs = spec.modelAttributes.get(model);
1557
1564
  const appAttrs = app.modelAttributes.get(model);
1558
1565
  if (specAttrs && appAttrs) {
@@ -1564,14 +1571,10 @@ function checkSchema(specSchema, appSchema) {
1564
1571
  const block = appBlocks.get(model);
1565
1572
  return block ? cascadesFromTenant(model, block) : [];
1566
1573
  });
1567
- const absentEnums = [];
1568
1574
  const missingEnumValues = [];
1569
1575
  for (const [name, specValues] of spec.enums) {
1570
1576
  const appValues = app.enums.get(name);
1571
- if (!appValues) {
1572
- absentEnums.push(name);
1573
- continue;
1574
- }
1577
+ if (!appValues) continue;
1575
1578
  for (const value of specValues) {
1576
1579
  if (!appValues.includes(value)) {
1577
1580
  missingEnumValues.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasicat/cli",
3
- "version": "1.0.0-rc.19",
3
+ "version": "1.0.0-rc.20",
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",
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "qrcode-terminal": "^0.12.0",
33
- "@saasicat/nest": "^1.0.0-rc.19",
34
- "@saasicat/spec": "^1.0.0-rc.19",
35
- "@saasicat/core": "^1.0.0-rc.19"
33
+ "@saasicat/nest": "^1.0.0-rc.20",
34
+ "@saasicat/spec": "^1.0.0-rc.20",
35
+ "@saasicat/core": "^1.0.0-rc.20"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@nestjs/common": "^11.0.0",