@saasicat/cli 1.0.0-rc.2 → 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/dist/index.cjs CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  DiscoverySnapshotDoctorCheck: () => DiscoverySnapshotDoctorCheck,
52
52
  DoctorCommands: () => DoctorCommands,
53
53
  DoctorFlow: () => DoctorFlow,
54
+ IssuerIdentityDoctorCheck: () => IssuerIdentityDoctorCheck,
54
55
  LIMIT_FILTER_IMPORTS: () => LIMIT_FILTER_IMPORTS,
55
56
  LIMIT_FILTER_PROVIDER: () => LIMIT_FILTER_PROVIDER,
56
57
  MANIFEST_ACCESS_PORT_TOKEN: () => MANIFEST_ACCESS_PORT_TOKEN,
@@ -64,17 +65,23 @@ __export(index_exports, {
64
65
  MfaSetupFlow: () => MfaSetupFlow,
65
66
  PLATFORM_DOCTOR_CHECK_PROVIDERS: () => PLATFORM_DOCTOR_CHECK_PROVIDERS,
66
67
  PlanCatalogDoctorCheck: () => PlanCatalogDoctorCheck,
68
+ SCANNED_FOR_DB_CATALOG: () => SCANNED_FOR_DB_CATALOG,
69
+ SCANNED_FOR_MOVED_SETTINGS: () => SCANNED_FOR_MOVED_SETTINGS,
70
+ SETTINGS_THAT_MOVED: () => SETTINGS_THAT_MOVED,
67
71
  UI_VUE_SPECIFIER: () => UI_VUE_SPECIFIER,
68
72
  USER_MANAGEMENT_PORT_TOKEN: () => USER_MANAGEMENT_PORT_TOKEN,
69
73
  USER_PORT_TOKEN: () => USER_PORT_TOKEN,
70
74
  UserCommands: () => UserCommands,
71
75
  UserPortDoctorCheck: () => UserPortDoctorCheck,
76
+ WHERE_DB_CATALOG_GOES: () => WHERE_DB_CATALOG_GOES,
77
+ WHERE_IT_GOES: () => WHERE_IT_GOES,
72
78
  WhoAmIFlow: () => WhoAmIFlow,
79
+ appKeyPattern: () => appKeyPattern,
73
80
  appendConstraints: () => appendConstraints,
74
81
  applyFragmentBlocks: () => applyFragmentBlocks,
75
82
  applyTokens: () => applyTokens,
76
83
  assertModelsExist: () => assertModelsExist,
77
- assertValidProjectKey: () => assertValidProjectKey,
84
+ assertValidAppKey: () => assertValidAppKey,
78
85
  assertValidQuotaKey: () => assertValidQuotaKey,
79
86
  blankStringLiterals: () => blankStringLiterals,
80
87
  blockBodyLines: () => blockBodyLines,
@@ -82,6 +89,7 @@ __export(index_exports, {
82
89
  buildImportMap: () => buildImportMap,
83
90
  checkSchema: () => checkSchema,
84
91
  constraintsFor: () => constraintsFor,
92
+ describeDbCatalogOccurrence: () => describeDbCatalogOccurrence,
85
93
  enableFkPointers: () => enableFkPointers,
86
94
  extractBlockNames: () => extractBlockNames,
87
95
  extractBlocks: () => extractBlocks,
@@ -90,7 +98,9 @@ __export(index_exports, {
90
98
  extractFragmentBlocks: () => extractFragmentBlocks,
91
99
  extractModelBlocks: () => extractModelBlocks,
92
100
  extractModelNames: () => extractModelNames,
101
+ findDbCatalogBlocks: () => findDbCatalogBlocks,
93
102
  findFkPointers: () => findFkPointers,
103
+ findMovedSettings: () => findMovedSettings,
94
104
  foreignKeyOf: () => foreignKeyOf,
95
105
  hasBackRelation: () => hasBackRelation,
96
106
  hasConstraints: () => hasConstraints,
@@ -100,6 +110,7 @@ __export(index_exports, {
100
110
  kebabCase: () => kebabCase,
101
111
  migrationCreatedBy: () => migrationCreatedBy,
102
112
  minimumQuotasPerPlan: () => minimumQuotasPerPlan,
113
+ modelsKeptPastTheTenant: () => modelsKeptPastTheTenant,
103
114
  namedImports: () => namedImports,
104
115
  parseBlockAttributes: () => parseBlockAttributes,
105
116
  parseEnumValues: () => parseEnumValues,
@@ -110,15 +121,16 @@ __export(index_exports, {
110
121
  patchAppModule: () => patchAppModule,
111
122
  patchOptionsFor: () => patchOptionsFor,
112
123
  planInit: () => planInit,
113
- projectKeyPattern: () => projectKeyPattern,
114
124
  quotaKeyPattern: () => quotaKeyPattern,
115
125
  readEffectiveModuleResolution: () => readEffectiveModuleResolution,
116
126
  relationNameOf: () => relationNameOf,
127
+ removeProjectKey: () => removeProjectKey,
117
128
  reportConstraints: () => reportConstraints,
118
129
  rewriteImports: () => rewriteImports,
119
130
  rewriteManifest: () => rewriteManifest,
120
131
  rewriteNames: () => rewriteNames,
121
132
  rewriteSubpath: () => rewriteSubpath,
133
+ settingsWrittenTo: () => settingsWrittenTo,
122
134
  stripLineComment: () => stripLineComment,
123
135
  structuralOnly: () => structuralOnly,
124
136
  tablesAddressedBy: () => tablesAddressedBy
@@ -1033,7 +1045,7 @@ var PlanCatalogDoctorCheck = class {
1033
1045
  severity: "ok",
1034
1046
  message: `${plans.length} plan(s), ${this.catalog.features?.length ?? 0} feature(s) loaded.`,
1035
1047
  details: {
1036
- projectKey: this.catalog.projectKey,
1048
+ app: this.catalog.app.name,
1037
1049
  planIds: plans.map((p) => p.id)
1038
1050
  }
1039
1051
  };
@@ -1145,11 +1157,85 @@ AdminManifestDoctorCheck = _ts_decorate7([
1145
1157
  typeof import_nest4.AdminManifestService === "undefined" ? Object : import_nest4.AdminManifestService
1146
1158
  ])
1147
1159
  ], AdminManifestDoctorCheck);
1160
+ var IssuerIdentityDoctorCheck = class {
1161
+ static {
1162
+ __name(this, "IssuerIdentityDoctorCheck");
1163
+ }
1164
+ issuer;
1165
+ id = "platform.issuer-identity";
1166
+ label = "Issuer identity against the recorded one";
1167
+ constructor(issuer) {
1168
+ this.issuer = issuer;
1169
+ }
1170
+ async run() {
1171
+ const verdict = await this.issuer.inspect();
1172
+ if (verdict.kind === "refused") {
1173
+ return {
1174
+ severity: "error",
1175
+ message: verdict.refusal,
1176
+ // The number and the identifiers, not the rows: `DoctorReport`
1177
+ // is serialised as JSON, and the dates on a row would come out
1178
+ // as timestamps beside the same dates already written as days in
1179
+ // the message above.
1180
+ // One unknown answered once: `null` for both where the
1181
+ // contracts could not be read, rather than a null count beside
1182
+ // an empty list that reads as "none".
1183
+ details: verdict.running ? {
1184
+ runningContracts: verdict.running.total,
1185
+ named: verdict.running.contracts.map((row) => row.id)
1186
+ } : {
1187
+ runningContracts: null,
1188
+ named: null
1189
+ }
1190
+ };
1191
+ }
1192
+ if (verdict.kind === "not-compared") {
1193
+ return {
1194
+ severity: "warning",
1195
+ message: `The issuer in the file is compared with nothing \u2014 ${verdict.why} \u2014 so a start that names another legal entity than the last one is not refused.`
1196
+ };
1197
+ }
1198
+ const change = verdict.change;
1199
+ switch (change.kind) {
1200
+ case "none-named":
1201
+ return {
1202
+ severity: "ok",
1203
+ message: "No issuer is named, and none was recorded."
1204
+ };
1205
+ case "first-naming":
1206
+ return {
1207
+ severity: "ok",
1208
+ message: `'${change.identity.legalName}' is named for the first time; no contract can name another.`
1209
+ };
1210
+ case "corrected":
1211
+ return {
1212
+ severity: "ok",
1213
+ message: `A correction of '${change.recorded.legalName}' is declared: ${change.reason}. This is what the start found; whether it has been recorded is what \`GET /admin/settings\` shows, and \`issuer.correctionOf\` may be dropped once it does.`
1214
+ };
1215
+ case "unchanged": {
1216
+ const running = await this.issuer.runningContractCount();
1217
+ const weighedAgainst = running === null ? "" : ` ${running} contract(s) are running, and their issuer copies do not follow a change.`;
1218
+ return {
1219
+ severity: "ok",
1220
+ message: `'${change.identity.legalName}' is what the installation recorded.` + weighedAgainst + " Changing the legal name or a tax identifier needs `issuer.correctionOf` beside the values it replaces; the address and the contact details do not."
1221
+ };
1222
+ }
1223
+ }
1224
+ }
1225
+ };
1226
+ IssuerIdentityDoctorCheck = _ts_decorate7([
1227
+ (0, import_common7.Injectable)(),
1228
+ _ts_metadata7("design:type", Function),
1229
+ _ts_metadata7("design:paramtypes", [
1230
+ typeof import_nest4.IssuerIdentityInspector === "undefined" ? Object : import_nest4.IssuerIdentityInspector
1231
+ ])
1232
+ ], IssuerIdentityDoctorCheck);
1148
1233
  var PLATFORM_DOCTOR_CHECK_PROVIDERS = [
1149
1234
  PlanCatalogDoctorCheck,
1150
1235
  DiscoverySnapshotDoctorCheck,
1151
1236
  UserPortDoctorCheck,
1152
- AdminManifestDoctorCheck
1237
+ AdminManifestDoctorCheck,
1238
+ IssuerIdentityDoctorCheck
1153
1239
  ];
1154
1240
 
1155
1241
  // src/prisma-blocks.ts
@@ -1325,6 +1411,117 @@ function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
1325
1411
  }
1326
1412
  __name(applyFragmentBlocks, "applyFragmentBlocks");
1327
1413
 
1414
+ // src/fk-pointers.ts
1415
+ var POINTER = /^(\s*)\/\/\s*(\w+)(\s+)(Tenant|User)(\??)(\s+)(@relation\(.*\))\s*$/;
1416
+ function findFkPointers(schema2) {
1417
+ const found = [];
1418
+ let model = "";
1419
+ schema2.split("\n").forEach((text, line) => {
1420
+ const opening = /^model\s+(\w+)\s*\{/.exec(text);
1421
+ if (opening) model = opening[1];
1422
+ const match = POINTER.exec(text);
1423
+ if (match) found.push({
1424
+ line,
1425
+ target: match[4],
1426
+ model,
1427
+ text
1428
+ });
1429
+ });
1430
+ return found;
1431
+ }
1432
+ __name(findFkPointers, "findFkPointers");
1433
+ function relationNameOf(relationAttribute) {
1434
+ const match = /@relation\(\s*"([^"]+)"/.exec(relationAttribute);
1435
+ return match ? match[1] : null;
1436
+ }
1437
+ __name(relationNameOf, "relationNameOf");
1438
+ function escapeForRegExp(value) {
1439
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1440
+ }
1441
+ __name(escapeForRegExp, "escapeForRegExp");
1442
+ function modelBody(schema2, model) {
1443
+ const name = escapeForRegExp(model);
1444
+ const block = new RegExp(`(^|\\n)model\\s+${name}\\s*\\{([\\s\\S]*?)\\n\\}`, "m").exec(schema2);
1445
+ return block ? block[2] : null;
1446
+ }
1447
+ __name(modelBody, "modelBody");
1448
+ function isOneToOne(schema2, model, foreignKey) {
1449
+ const body = modelBody(schema2, model);
1450
+ if (!body) return false;
1451
+ return new RegExp(`^\\s*${escapeForRegExp(foreignKey)}\\s+\\S+.*@unique`, "m").test(body);
1452
+ }
1453
+ __name(isOneToOne, "isOneToOne");
1454
+ function hasBackRelation(schema2, model, owner, relationName, singular = false) {
1455
+ const body = modelBody(schema2, owner);
1456
+ if (body === null) return false;
1457
+ const name = escapeForRegExp(model);
1458
+ const shape = singular ? `${name}\\??` : `${name}\\[\\]`;
1459
+ const candidates = [
1460
+ ...body.matchAll(new RegExp(`^\\s*\\w+\\s+${shape}(\\s.*)?$`, "gm"))
1461
+ ];
1462
+ return candidates.some((line) => relationNameOf(line[0]) === relationName);
1463
+ }
1464
+ __name(hasBackRelation, "hasBackRelation");
1465
+ function enableFkPointers(schema2, models) {
1466
+ const lines = schema2.split("\n");
1467
+ const enabled = [];
1468
+ const skipped = [];
1469
+ const needsBackRelation = [];
1470
+ for (const pointer of findFkPointers(schema2)) {
1471
+ const model = pointer.target === "Tenant" ? models.tenant : models.user;
1472
+ if (!model) {
1473
+ skipped.push({
1474
+ line: pointer.line,
1475
+ target: pointer.target
1476
+ });
1477
+ continue;
1478
+ }
1479
+ const match = POINTER.exec(pointer.text);
1480
+ const relationName = relationNameOf(match[7]);
1481
+ const foreignKey = foreignKeyOf(match[7]);
1482
+ const singular = foreignKey !== null && isOneToOne(schema2, pointer.model, foreignKey);
1483
+ if (!hasBackRelation(schema2, pointer.model, model, relationName, singular)) {
1484
+ needsBackRelation.push({
1485
+ line: pointer.line,
1486
+ owner: model,
1487
+ suggestion: backRelationSuggestion(pointer.model, relationName, singular)
1488
+ });
1489
+ continue;
1490
+ }
1491
+ const [, indent, field, gap1, , optional, gap2, relation] = match;
1492
+ lines[pointer.line] = `${indent}${field}${gap1}${model}${optional}${gap2}${relation}`;
1493
+ enabled.push({
1494
+ line: pointer.line,
1495
+ model
1496
+ });
1497
+ }
1498
+ return {
1499
+ schema: lines.join("\n"),
1500
+ enabled,
1501
+ skipped,
1502
+ needsBackRelation
1503
+ };
1504
+ }
1505
+ __name(enableFkPointers, "enableFkPointers");
1506
+ var lowerFirst = /* @__PURE__ */ __name((value) => value.charAt(0).toLowerCase() + value.slice(1), "lowerFirst");
1507
+ function foreignKeyOf(relationAttribute) {
1508
+ const match = /fields:\s*\[\s*(\w+)/.exec(relationAttribute);
1509
+ return match ? match[1] : null;
1510
+ }
1511
+ __name(foreignKeyOf, "foreignKeyOf");
1512
+ function backRelationSuggestion(model, relationName, singular) {
1513
+ const field = singular ? lowerFirst(model) : `${lowerFirst(model)}s`;
1514
+ const type = singular ? `${model}?` : `${model}[]`;
1515
+ return `${field} ${type}` + (relationName ? ` @relation("${relationName}")` : "");
1516
+ }
1517
+ __name(backRelationSuggestion, "backRelationSuggestion");
1518
+ function assertModelsExist(declaredModels, models) {
1519
+ const missing = Object.entries(models).filter(([, name]) => name && !declaredModels.includes(name)).map(([role, name]) => `--${role}-model=${name}`);
1520
+ if (missing.length === 0) return;
1521
+ throw new Error(`${missing.join(", ")} \u2014 no such model in this schema. It declares: ${declaredModels.slice().sort().join(", ")}.`);
1522
+ }
1523
+ __name(assertModelsExist, "assertModelsExist");
1524
+
1328
1525
  // src/schema-check.ts
1329
1526
  function renderType(signature) {
1330
1527
  return `${signature.type}${signature.list ? "[]" : ""}${signature.optional ? "?" : ""}`;
@@ -1396,10 +1593,11 @@ function isDocumentedTypeSubstitution(spec, app, appEnums) {
1396
1593
  return spec.type === "String" && appEnums.has(app.type);
1397
1594
  }
1398
1595
  __name(isDocumentedTypeSubstitution, "isDocumentedTypeSubstitution");
1399
- function compareFields(model, specFields, appFields, appEnums, missingFields, fieldMismatches) {
1596
+ function compareFields(model, specFields, appFields, appEnums, notAdopted, missingFields, fieldMismatches) {
1400
1597
  for (const [name, spec] of specFields) {
1401
1598
  const app = appFields.get(name);
1402
1599
  if (!app) {
1600
+ if (notAdopted.has(spec.type)) continue;
1403
1601
  missingFields.push({
1404
1602
  model,
1405
1603
  field: name,
@@ -1457,34 +1655,62 @@ function breaksContract(attribute) {
1457
1655
  return attribute.kind !== "index";
1458
1656
  }
1459
1657
  __name(breaksContract, "breaksContract");
1460
- function checkSchema(specSchema, appSchema) {
1658
+ function modelsKeptPastTheTenant(specSchema) {
1659
+ const pointed = new Set(findFkPointers(specSchema).filter((pointer) => pointer.target === "Tenant").map((pointer) => pointer.model));
1660
+ return [
1661
+ ...parseSchema(specSchema).models
1662
+ ].filter(([model, fields]) => fields.has("tenantId") && !pointed.has(model)).map(([model]) => model);
1663
+ }
1664
+ __name(modelsKeptPastTheTenant, "modelsKeptPastTheTenant");
1665
+ function cascadesFromTenant(model, block) {
1666
+ const found = [];
1667
+ for (const line of block.split("\n")) {
1668
+ const compact = structuralOnly(line).replace(/\s+/g, "");
1669
+ if (compact.includes("@relation(") && compact.includes("fields:[tenantId]") && compact.includes("onDelete:Cascade")) {
1670
+ found.push({
1671
+ model,
1672
+ field: line.trim().split(/\s/)[0]
1673
+ });
1674
+ }
1675
+ }
1676
+ return found;
1677
+ }
1678
+ __name(cascadesFromTenant, "cascadesFromTenant");
1679
+ function checkSchema(specSchema, appSchema, knownModels) {
1461
1680
  const spec = parseSchema(specSchema);
1462
1681
  const app = parseSchema(appSchema);
1463
- const absentModels = [];
1464
1682
  const missingFields = [];
1465
1683
  const fieldMismatches = [];
1466
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)));
1467
1695
  for (const [model, specFields] of spec.models) {
1468
1696
  const appFields = app.models.get(model);
1469
- if (!appFields) {
1470
- absentModels.push(model);
1471
- continue;
1472
- }
1473
- compareFields(model, specFields, appFields, app.enums, missingFields, fieldMismatches);
1697
+ if (!appFields) continue;
1698
+ compareFields(model, specFields, appFields, app.enums, notAdopted, missingFields, fieldMismatches);
1474
1699
  const specAttrs = spec.modelAttributes.get(model);
1475
1700
  const appAttrs = app.modelAttributes.get(model);
1476
1701
  if (specAttrs && appAttrs) {
1477
1702
  compareBlockAttributes(model, specAttrs, appAttrs, missingBlockAttributes);
1478
1703
  }
1479
1704
  }
1480
- const absentEnums = [];
1705
+ const appBlocks = extractBlocks(appSchema, "model");
1706
+ const tenantCascades = modelsKeptPastTheTenant(specSchema).flatMap((model) => {
1707
+ const block = appBlocks.get(model);
1708
+ return block ? cascadesFromTenant(model, block) : [];
1709
+ });
1481
1710
  const missingEnumValues = [];
1482
1711
  for (const [name, specValues] of spec.enums) {
1483
1712
  const appValues = app.enums.get(name);
1484
- if (!appValues) {
1485
- absentEnums.push(name);
1486
- continue;
1487
- }
1713
+ if (!appValues) continue;
1488
1714
  for (const value of specValues) {
1489
1715
  if (!appValues.includes(value)) {
1490
1716
  missingEnumValues.push({
@@ -1501,9 +1727,10 @@ function checkSchema(specSchema, appSchema) {
1501
1727
  missingEnumValues,
1502
1728
  fieldMismatches,
1503
1729
  missingBlockAttributes,
1730
+ tenantCascades,
1504
1731
  checkedModelCount: spec.models.size - absentModels.length,
1505
1732
  checkedEnumCount: spec.enums.size - absentEnums.length,
1506
- ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0 && !missingBlockAttributes.some(breaksContract)
1733
+ ok: missingFields.length === 0 && missingEnumValues.length === 0 && fieldMismatches.length === 0 && tenantCascades.length === 0 && !missingBlockAttributes.some(breaksContract)
1507
1734
  };
1508
1735
  }
1509
1736
  __name(checkSchema, "checkSchema");
@@ -1584,117 +1811,6 @@ function reportConstraints(outcome, context) {
1584
1811
  }
1585
1812
  __name(reportConstraints, "reportConstraints");
1586
1813
 
1587
- // src/fk-pointers.ts
1588
- var POINTER = /^(\s*)\/\/\s*(\w+)(\s+)(Tenant|User)(\??)(\s+)(@relation\(.*\))\s*$/;
1589
- function findFkPointers(schema2) {
1590
- const found = [];
1591
- let model = "";
1592
- schema2.split("\n").forEach((text, line) => {
1593
- const opening = /^model\s+(\w+)\s*\{/.exec(text);
1594
- if (opening) model = opening[1];
1595
- const match = POINTER.exec(text);
1596
- if (match) found.push({
1597
- line,
1598
- target: match[4],
1599
- model,
1600
- text
1601
- });
1602
- });
1603
- return found;
1604
- }
1605
- __name(findFkPointers, "findFkPointers");
1606
- function relationNameOf(relationAttribute) {
1607
- const match = /@relation\(\s*"([^"]+)"/.exec(relationAttribute);
1608
- return match ? match[1] : null;
1609
- }
1610
- __name(relationNameOf, "relationNameOf");
1611
- function escapeForRegExp(value) {
1612
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1613
- }
1614
- __name(escapeForRegExp, "escapeForRegExp");
1615
- function modelBody(schema2, model) {
1616
- const name = escapeForRegExp(model);
1617
- const block = new RegExp(`(^|\\n)model\\s+${name}\\s*\\{([\\s\\S]*?)\\n\\}`, "m").exec(schema2);
1618
- return block ? block[2] : null;
1619
- }
1620
- __name(modelBody, "modelBody");
1621
- function isOneToOne(schema2, model, foreignKey) {
1622
- const body = modelBody(schema2, model);
1623
- if (!body) return false;
1624
- return new RegExp(`^\\s*${escapeForRegExp(foreignKey)}\\s+\\S+.*@unique`, "m").test(body);
1625
- }
1626
- __name(isOneToOne, "isOneToOne");
1627
- function hasBackRelation(schema2, model, owner, relationName, singular = false) {
1628
- const body = modelBody(schema2, owner);
1629
- if (body === null) return false;
1630
- const name = escapeForRegExp(model);
1631
- const shape = singular ? `${name}\\??` : `${name}\\[\\]`;
1632
- const candidates = [
1633
- ...body.matchAll(new RegExp(`^\\s*\\w+\\s+${shape}(\\s.*)?$`, "gm"))
1634
- ];
1635
- return candidates.some((line) => relationNameOf(line[0]) === relationName);
1636
- }
1637
- __name(hasBackRelation, "hasBackRelation");
1638
- function enableFkPointers(schema2, models) {
1639
- const lines = schema2.split("\n");
1640
- const enabled = [];
1641
- const skipped = [];
1642
- const needsBackRelation = [];
1643
- for (const pointer of findFkPointers(schema2)) {
1644
- const model = pointer.target === "Tenant" ? models.tenant : models.user;
1645
- if (!model) {
1646
- skipped.push({
1647
- line: pointer.line,
1648
- target: pointer.target
1649
- });
1650
- continue;
1651
- }
1652
- const match = POINTER.exec(pointer.text);
1653
- const relationName = relationNameOf(match[7]);
1654
- const foreignKey = foreignKeyOf(match[7]);
1655
- const singular = foreignKey !== null && isOneToOne(schema2, pointer.model, foreignKey);
1656
- if (!hasBackRelation(schema2, pointer.model, model, relationName, singular)) {
1657
- needsBackRelation.push({
1658
- line: pointer.line,
1659
- owner: model,
1660
- suggestion: backRelationSuggestion(pointer.model, relationName, singular)
1661
- });
1662
- continue;
1663
- }
1664
- const [, indent, field, gap1, , optional, gap2, relation] = match;
1665
- lines[pointer.line] = `${indent}${field}${gap1}${model}${optional}${gap2}${relation}`;
1666
- enabled.push({
1667
- line: pointer.line,
1668
- model
1669
- });
1670
- }
1671
- return {
1672
- schema: lines.join("\n"),
1673
- enabled,
1674
- skipped,
1675
- needsBackRelation
1676
- };
1677
- }
1678
- __name(enableFkPointers, "enableFkPointers");
1679
- var lowerFirst = /* @__PURE__ */ __name((value) => value.charAt(0).toLowerCase() + value.slice(1), "lowerFirst");
1680
- function foreignKeyOf(relationAttribute) {
1681
- const match = /fields:\s*\[\s*(\w+)/.exec(relationAttribute);
1682
- return match ? match[1] : null;
1683
- }
1684
- __name(foreignKeyOf, "foreignKeyOf");
1685
- function backRelationSuggestion(model, relationName, singular) {
1686
- const field = singular ? lowerFirst(model) : `${lowerFirst(model)}s`;
1687
- const type = singular ? `${model}?` : `${model}[]`;
1688
- return `${field} ${type}` + (relationName ? ` @relation("${relationName}")` : "");
1689
- }
1690
- __name(backRelationSuggestion, "backRelationSuggestion");
1691
- function assertModelsExist(declaredModels, models) {
1692
- const missing = Object.entries(models).filter(([, name]) => name && !declaredModels.includes(name)).map(([role, name]) => `--${role}-model=${name}`);
1693
- if (missing.length === 0) return;
1694
- throw new Error(`${missing.join(", ")} \u2014 no such model in this schema. It declares: ${declaredModels.slice().sort().join(", ")}.`);
1695
- }
1696
- __name(assertModelsExist, "assertModelsExist");
1697
-
1698
1814
  // src/init/catalog-keys.ts
1699
1815
  var import_spec = require("@saasicat/spec");
1700
1816
  var schema = import_spec.planCatalogSchema;
@@ -1705,10 +1821,11 @@ function required(value, what) {
1705
1821
  return value;
1706
1822
  }
1707
1823
  __name(required, "required");
1708
- function projectKeyPattern() {
1709
- return new RegExp(required(schema.properties?.projectKey?.pattern, "pattern for projectKey"));
1824
+ var APP_KEY_PATTERN = /^[a-z][a-z0-9-]{1,30}$/;
1825
+ function appKeyPattern() {
1826
+ return APP_KEY_PATTERN;
1710
1827
  }
1711
- __name(projectKeyPattern, "projectKeyPattern");
1828
+ __name(appKeyPattern, "appKeyPattern");
1712
1829
  function quotaKeyPattern() {
1713
1830
  const patterns = Object.keys(required(schema.$defs?.PlanDef?.properties?.quotas?.patternProperties, "quota key pattern"));
1714
1831
  if (patterns.length !== 1) {
@@ -1721,12 +1838,12 @@ function minimumQuotasPerPlan() {
1721
1838
  return required(schema.$defs?.PlanDef?.properties?.quotas?.minProperties, "minProperties");
1722
1839
  }
1723
1840
  __name(minimumQuotasPerPlan, "minimumQuotasPerPlan");
1724
- function assertValidProjectKey(projectKey) {
1725
- const pattern = projectKeyPattern();
1726
- if (pattern.test(projectKey)) return;
1727
- throw new Error(`--project-key=${projectKey} is not a valid project key. It has to match ${pattern.source} \u2014 lower case, starting with a letter, at least two characters. The platform validates config/saas.yaml against the same pattern at boot, so this would fail after every file was written.`);
1841
+ function assertValidAppKey(appKey) {
1842
+ const pattern = appKeyPattern();
1843
+ if (pattern.test(appKey)) return;
1844
+ throw new Error(`--app-key=${appKey} is not a valid app key. It has to match ${pattern.source} \u2014 lower case, starting with a letter, at least two characters. It becomes an npm package name and a browser storage prefix, so this would fail after every file was written.`);
1728
1845
  }
1729
- __name(assertValidProjectKey, "assertValidProjectKey");
1846
+ __name(assertValidAppKey, "assertValidAppKey");
1730
1847
  function assertValidQuotaKey(quotaKey) {
1731
1848
  const pattern = quotaKeyPattern();
1732
1849
  if (pattern.test(quotaKey)) return;
@@ -1758,24 +1875,24 @@ function pascalCase(value) {
1758
1875
  __name(pascalCase, "pascalCase");
1759
1876
  var quotaFileName = /* @__PURE__ */ __name((key) => `${key.replace(/[^A-Za-z0-9]+/g, "-").toLowerCase()}-quota.provider.ts`, "quotaFileName");
1760
1877
  function planInit(options) {
1761
- const projectKey = options.projectKey;
1762
- if (!projectKey) throw new Error("init needs a --project-key.");
1763
- assertValidProjectKey(projectKey);
1764
- const appLabel = options.appName ?? pascalCase(projectKey);
1878
+ const appKey = options.appKey;
1879
+ if (!appKey) throw new Error("init needs an --app-key.");
1880
+ assertValidAppKey(appKey);
1881
+ const appLabel = options.appName ?? pascalCase(appKey);
1765
1882
  const appName = pascalCase(appLabel);
1766
1883
  const apiBase = options.apiBase ?? "/api/v1/admin";
1767
1884
  const quotas = (options.quotas ?? []).map(parseQuota);
1768
1885
  assertEnoughQuotas(quotas);
1769
1886
  const hasherClass = options.skipHasher ? null : `${appName}PasswordHasher`;
1770
- const featureKey = `${projectKey.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_CORE`;
1887
+ const featureKey = `${appKey.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}_CORE`;
1771
1888
  const shared = {
1772
- PROJECT_KEY: projectKey,
1889
+ APP_KEY: appKey,
1773
1890
  APP_NAME: appName,
1774
1891
  APP_LABEL: appLabel,
1775
1892
  API_BASE: apiBase,
1776
1893
  FEATURE_KEY: featureKey,
1777
- REGISTRY_CONST: `${constantCase(projectKey)}_FEATURE_UI_REGISTRY`,
1778
- MANIFEST_CONST: `${constantCase(projectKey)}_MANIFEST_CONTRIBUTION`,
1894
+ REGISTRY_CONST: `${constantCase(appKey)}_FEATURE_UI_REGISTRY`,
1895
+ MANIFEST_CONST: `${constantCase(appKey)}_MANIFEST_CONTRIBUTION`,
1779
1896
  ADMIN_MODULE_CLASS: `${appName}AdminModule`,
1780
1897
  HASHER_CLASS: hasherClass ?? "",
1781
1898
  HASHER_FILE: hasherClass ? `${kebabCase(appName)}-password.hasher` : "",
@@ -1906,6 +2023,31 @@ function readEffectiveModuleResolution(root, ts) {
1906
2023
  }
1907
2024
  __name(readEffectiveModuleResolution, "readEffectiveModuleResolution");
1908
2025
 
2026
+ // src/init/settings-written.ts
2027
+ var import_billing = require("@saasicat/nest/billing");
2028
+ function flatten(prefix, value, into) {
2029
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
2030
+ for (const [key, child] of Object.entries(value)) {
2031
+ flatten(`${prefix}.${key}`, child, into);
2032
+ }
2033
+ return;
2034
+ }
2035
+ into.push({
2036
+ key: prefix,
2037
+ value: JSON.stringify(value)
2038
+ });
2039
+ }
2040
+ __name(flatten, "flatten");
2041
+ function settingsWrittenTo(catalogYaml, source = "config/saas.yaml") {
2042
+ const catalog = (0, import_billing.loadPlanCatalogFromString)(catalogYaml, {
2043
+ source
2044
+ });
2045
+ const settings = [];
2046
+ flatten("tenantBilling", catalog.tenantBilling, settings);
2047
+ return settings;
2048
+ }
2049
+ __name(settingsWrittenTo, "settingsWrittenTo");
2050
+
1909
2051
  // src/codemods/v1-imports.ts
1910
2052
  var PUBLIC_PREFIXES = [
1911
2053
  "ui/",
@@ -2169,6 +2311,327 @@ function rewriteManifest(text, table, options) {
2169
2311
  }
2170
2312
  __name(rewriteManifest, "rewriteManifest");
2171
2313
 
2314
+ // src/codemods/v1-project-key.ts
2315
+ function stripQueryParameter(text) {
2316
+ const NEEDLE = "projectKey=";
2317
+ let out = "";
2318
+ let index = 0;
2319
+ const taken = /* @__PURE__ */ new Set();
2320
+ for (; ; ) {
2321
+ const at = text.indexOf(NEEDLE, index);
2322
+ if (at < 0) break;
2323
+ const separator = at === 0 ? "" : text[at - 1];
2324
+ const value = separator === "?" || separator === "&" ? simpleValueEnd(text, at + NEEDLE.length) : null;
2325
+ if (value === null || !servesTheCatalogue(text, at)) {
2326
+ out += text.slice(index, at + NEEDLE.length);
2327
+ index = at + NEEDLE.length;
2328
+ continue;
2329
+ }
2330
+ out += text.slice(index, at - 1);
2331
+ if (separator === "?" && text[value] === "&") {
2332
+ out += "?";
2333
+ index = value + 1;
2334
+ } else {
2335
+ index = value;
2336
+ }
2337
+ taken.add(at);
2338
+ }
2339
+ return {
2340
+ text: out + text.slice(index),
2341
+ taken
2342
+ };
2343
+ }
2344
+ __name(stripQueryParameter, "stripQueryParameter");
2345
+ var HIDES_A_BRACE = /[`'"{}/\\]/;
2346
+ function simpleValueEnd(text, from) {
2347
+ let i = from;
2348
+ while (i < text.length) {
2349
+ const ch = text[i];
2350
+ if (ch === "&" || ch === "#" || ch === "'" || ch === '"' || ch === "`" || ch === "\n" || ch === " ") {
2351
+ return i;
2352
+ }
2353
+ if (ch !== "$" || text[i + 1] !== "{") {
2354
+ if (ch === "{" || ch === "}") return null;
2355
+ i += 1;
2356
+ continue;
2357
+ }
2358
+ const close = text.indexOf("}", i + 2);
2359
+ if (close < 0) return null;
2360
+ if (HIDES_A_BRACE.test(text.slice(i + 2, close))) return null;
2361
+ i = close + 1;
2362
+ }
2363
+ return null;
2364
+ }
2365
+ __name(simpleValueEnd, "simpleValueEnd");
2366
+ function servesTheCatalogue(text, at) {
2367
+ let start = at;
2368
+ while (start > 0) {
2369
+ const ch = text[start - 1];
2370
+ if (ch === "`" || ch === "'" || ch === '"' || ch === "\n") break;
2371
+ start -= 1;
2372
+ }
2373
+ const url = text.slice(start, at);
2374
+ const query = url.indexOf("?");
2375
+ return (query < 0 ? url : url.slice(0, query)).includes("/catalog/");
2376
+ }
2377
+ __name(servesTheCatalogue, "servesTheCatalogue");
2378
+ function isIdentifierChar(ch) {
2379
+ return ch !== void 0 && /[A-Za-z0-9_$]/.test(ch);
2380
+ }
2381
+ __name(isIdentifierChar, "isIdentifierChar");
2382
+ function lineAt(text, at) {
2383
+ let line = 1;
2384
+ for (let i = 0; i < at; i += 1) if (text[i] === "\n") line += 1;
2385
+ return line;
2386
+ }
2387
+ __name(lineAt, "lineAt");
2388
+ function removeProjectKey(text, kind = "source") {
2389
+ if (kind === "yaml") return removeFromYaml(text);
2390
+ const query = stripQueryParameter(text);
2391
+ const reported = /* @__PURE__ */ new Set();
2392
+ for (let at = text.indexOf("projectKey"); at >= 0; at = text.indexOf("projectKey", at + 1)) {
2393
+ if (query.taken.has(at)) continue;
2394
+ if (isIdentifierChar(text[at - 1])) continue;
2395
+ if (isIdentifierChar(text[at + "projectKey".length])) continue;
2396
+ reported.add(lineAt(text, at));
2397
+ }
2398
+ return {
2399
+ text: query.text,
2400
+ rewritten: query.taken.size,
2401
+ undecided: [
2402
+ ...reported
2403
+ ].sort((a, b) => a - b)
2404
+ };
2405
+ }
2406
+ __name(removeProjectKey, "removeProjectKey");
2407
+ function removeFromYaml(text) {
2408
+ const lines = text.split("\n");
2409
+ const kept = [];
2410
+ let rewritten = 0;
2411
+ for (const line of lines) {
2412
+ if (line.startsWith("projectKey:")) {
2413
+ rewritten += 1;
2414
+ continue;
2415
+ }
2416
+ kept.push(line);
2417
+ }
2418
+ return {
2419
+ text: kept.join("\n"),
2420
+ rewritten,
2421
+ undecided: []
2422
+ };
2423
+ }
2424
+ __name(removeFromYaml, "removeFromYaml");
2425
+
2426
+ // src/codemods/v1-moved-settings.ts
2427
+ var import_spec2 = require("@saasicat/spec");
2428
+ var SETTINGS_THAT_MOVED = Object.keys(import_spec2.planCatalogSchema.properties?.tenantBilling?.properties ?? (() => {
2429
+ throw new Error("plan-catalog.schema.json declares no tenantBilling properties \u2014 @saasicat/spec and @saasicat/cli are out of step.");
2430
+ })());
2431
+ var isIdentifierChar2 = /* @__PURE__ */ __name((ch) => ch !== void 0 && /[A-Za-z0-9_$]/.test(ch), "isIdentifierChar");
2432
+ function lineAt2(text, index) {
2433
+ let line = 1;
2434
+ for (let at = 0; at < index; at += 1) {
2435
+ if (text[at] === "\n") line += 1;
2436
+ }
2437
+ return line;
2438
+ }
2439
+ __name(lineAt2, "lineAt");
2440
+ var SCANNED_FOR_MOVED_SETTINGS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue)$/;
2441
+ function findMovedSettings(text) {
2442
+ const occurrences = [];
2443
+ for (const setting of SETTINGS_THAT_MOVED) {
2444
+ for (let at = text.indexOf(setting); at >= 0; at = text.indexOf(setting, at + 1)) {
2445
+ if (isIdentifierChar2(text[at - 1])) continue;
2446
+ if (isIdentifierChar2(text[at + setting.length])) continue;
2447
+ occurrences.push({
2448
+ setting,
2449
+ line: lineAt2(text, at)
2450
+ });
2451
+ }
2452
+ }
2453
+ return {
2454
+ occurrences: occurrences.sort((a, b) => a.line - b.line || a.setting.localeCompare(b.setting))
2455
+ };
2456
+ }
2457
+ __name(findMovedSettings, "findMovedSettings");
2458
+ var WHERE_IT_GOES = {
2459
+ cancellationNoticeDays: "config/saas.yaml \u2192 tenantBilling.cancellationNoticeDays \u2014 both `monthly` and `yearly` are required, so write the number you are running today rather than leaving one out.",
2460
+ selfServiceBlockedPlans: 'config/saas.yaml \u2192 tenantBilling.selfServiceBlockedPlans \u2014 both `asTarget` and `asSource` are required, and `[]` is the way to say "nothing is blocked".'
2461
+ };
2462
+
2463
+ // src/codemods/v1-db-catalog.ts
2464
+ var import_platform = require("@saasicat/nest/platform");
2465
+ var SCANNED_FOR_DB_CATALOG = SCANNED_FOR_MOVED_SETTINGS;
2466
+ var PROPERTY = "dbCatalog";
2467
+ var TAKEN = new Set(import_platform.DB_CATALOG_MEMBERS);
2468
+ var isIdentifierStart = /* @__PURE__ */ __name((ch) => ch !== void 0 && /[A-Za-z_$]/.test(ch), "isIdentifierStart");
2469
+ var isIdentifierChar3 = /* @__PURE__ */ __name((ch) => ch !== void 0 && /[A-Za-z0-9_$]/.test(ch), "isIdentifierChar");
2470
+ var isBlank = /* @__PURE__ */ __name((ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r", "isBlank");
2471
+ function lineAt3(text, index) {
2472
+ let line = 1;
2473
+ for (let at = 0; at < index; at += 1) {
2474
+ if (text[at] === "\n") line += 1;
2475
+ }
2476
+ return line;
2477
+ }
2478
+ __name(lineAt3, "lineAt");
2479
+ function skipBlanks(text, from) {
2480
+ let at = from;
2481
+ while (isBlank(text[at])) at += 1;
2482
+ return at;
2483
+ }
2484
+ __name(skipBlanks, "skipBlanks");
2485
+ function closingQuote(text, open) {
2486
+ const quote = text[open];
2487
+ for (let at = open + 1; at < text.length; at += 1) {
2488
+ if (text[at] === "\\") {
2489
+ at += 1;
2490
+ continue;
2491
+ }
2492
+ if (text[at] === quote) return at;
2493
+ }
2494
+ return text.length;
2495
+ }
2496
+ __name(closingQuote, "closingQuote");
2497
+ function skipCommentOrString(text, at) {
2498
+ const ch = text[at];
2499
+ if (ch === "/" && text[at + 1] === "/") {
2500
+ const end = text.indexOf("\n", at);
2501
+ return end === -1 ? text.length : end;
2502
+ }
2503
+ if (ch === "/" && text[at + 1] === "*") {
2504
+ const end = text.indexOf("*/", at + 2);
2505
+ return end === -1 ? text.length : end + 2;
2506
+ }
2507
+ if (ch === "'" || ch === '"' || ch === "`") return closingQuote(text, at) + 1;
2508
+ return at;
2509
+ }
2510
+ __name(skipCommentOrString, "skipCommentOrString");
2511
+ function objectLiteralAt(text, open) {
2512
+ const members = [];
2513
+ let depth = 0;
2514
+ let expectingKey = true;
2515
+ let at = open + 1;
2516
+ while (at < text.length) {
2517
+ const ch = text[at];
2518
+ if (ch === "'" || ch === '"' || ch === "`") {
2519
+ const end = closingQuote(text, at);
2520
+ if (depth === 0 && expectingKey && text[skipBlanks(text, end + 1)] === ":") {
2521
+ members.push(text.slice(at + 1, end));
2522
+ }
2523
+ at = end + 1;
2524
+ continue;
2525
+ }
2526
+ const skipped = skipCommentOrString(text, at);
2527
+ if (skipped > at) {
2528
+ at = skipped;
2529
+ continue;
2530
+ }
2531
+ if (ch === "{" || ch === "[" || ch === "(") {
2532
+ depth += 1;
2533
+ at += 1;
2534
+ continue;
2535
+ }
2536
+ if (ch === "}" || ch === "]" || ch === ")") {
2537
+ if (depth === 0) return {
2538
+ members,
2539
+ end: at + 1
2540
+ };
2541
+ depth -= 1;
2542
+ at += 1;
2543
+ continue;
2544
+ }
2545
+ if (depth === 0 && ch === ":") expectingKey = false;
2546
+ if (depth === 0 && ch === ",") expectingKey = true;
2547
+ if (depth === 0 && expectingKey && ch === "." && text.startsWith("...", at)) {
2548
+ let end = at + 3;
2549
+ while (isIdentifierChar3(text[end])) end += 1;
2550
+ members.push(text.slice(at, end));
2551
+ at = end;
2552
+ continue;
2553
+ }
2554
+ if (depth === 0 && expectingKey && isIdentifierStart(ch)) {
2555
+ let end = at;
2556
+ while (isIdentifierChar3(text[end])) end += 1;
2557
+ const next = text[skipBlanks(text, end)];
2558
+ if (next === ":" || next === "," || next === "}") members.push(text.slice(at, end));
2559
+ at = end;
2560
+ continue;
2561
+ }
2562
+ at += 1;
2563
+ }
2564
+ return {
2565
+ members,
2566
+ end: text.length
2567
+ };
2568
+ }
2569
+ __name(objectLiteralAt, "objectLiteralAt");
2570
+ function findDbCatalogBlocks(text) {
2571
+ const occurrences = [];
2572
+ let at = 0;
2573
+ while (at < text.length) {
2574
+ const skipped = skipCommentOrString(text, at);
2575
+ if (skipped > at) {
2576
+ at = skipped;
2577
+ continue;
2578
+ }
2579
+ if (!text.startsWith(PROPERTY, at) || isIdentifierChar3(text[at - 1])) {
2580
+ at += 1;
2581
+ continue;
2582
+ }
2583
+ const afterName = at + PROPERTY.length;
2584
+ if (isIdentifierChar3(text[afterName])) {
2585
+ at = afterName;
2586
+ continue;
2587
+ }
2588
+ const colon = skipBlanks(text, afterName);
2589
+ if (text[colon] !== ":") {
2590
+ at = afterName;
2591
+ continue;
2592
+ }
2593
+ const value = skipBlanks(text, colon + 1);
2594
+ const line = lineAt3(text, at);
2595
+ if (text[value] !== "{") {
2596
+ occurrences.push({
2597
+ line,
2598
+ shape: "reference",
2599
+ leftovers: []
2600
+ });
2601
+ at = value;
2602
+ continue;
2603
+ }
2604
+ const { members, end } = objectLiteralAt(text, value);
2605
+ const leftovers = members.filter((member) => !TAKEN.has(member));
2606
+ if (!members.includes("path")) {
2607
+ occurrences.push({
2608
+ line,
2609
+ shape: "values",
2610
+ leftovers
2611
+ });
2612
+ } else if (leftovers.length > 0) {
2613
+ occurrences.push({
2614
+ line,
2615
+ shape: "mixed",
2616
+ leftovers
2617
+ });
2618
+ }
2619
+ at = end;
2620
+ }
2621
+ return {
2622
+ occurrences
2623
+ };
2624
+ }
2625
+ __name(findDbCatalogBlocks, "findDbCatalogBlocks");
2626
+ function describeDbCatalogOccurrence(occurrence) {
2627
+ const { shape, leftovers } = occurrence;
2628
+ if (shape === "reference") return "carries something this cannot see into";
2629
+ if (shape === "mixed") return `names the path but still carries ${leftovers.join(", ")}`;
2630
+ return leftovers.length > 0 ? `carries the values: ${leftovers.join(", ")}` : "names no path";
2631
+ }
2632
+ __name(describeDbCatalogOccurrence, "describeDbCatalogOccurrence");
2633
+ var WHERE_DB_CATALOG_GOES = "dbCatalog: { path: 'config/saas.yaml' } \u2014 the file the values were forwarded from. Delete the values; the platform reads app, currency, vatRate, tenantBilling, marketing and notifications from the file it names, and the plans from the read sink as before.";
2634
+
2172
2635
  // src/init/patch-app-module.ts
2173
2636
  var MARKER = "SaaSiCatModule.forRoot";
2174
2637
  function patchAppModule(source, options) {
@@ -2268,7 +2731,7 @@ function renderForRootBlock(options) {
2268
2731
  "SaaSiCatModule.forRoot(",
2269
2732
  " defineSaaSiCat({",
2270
2733
  " // Plans straight from the YAML. Apps that manage plans in the",
2271
- " // SuperAdmin UI pass `dbCatalog` instead.",
2734
+ " // SuperAdmin UI pass `dbCatalog: { path: 'config/saas.yaml' }` instead.",
2272
2735
  " planCatalog: loadPlanCatalogFromFile({ path: 'config/saas.yaml' }),",
2273
2736
  " // Your authentication guard. This does NOT compile until you",
2274
2737
  " // name one, and that is deliberate: an empty array is how the",
@@ -3373,6 +3836,7 @@ UserCommands = _ts_decorate14([
3373
3836
  DiscoverySnapshotDoctorCheck,
3374
3837
  DoctorCommands,
3375
3838
  DoctorFlow,
3839
+ IssuerIdentityDoctorCheck,
3376
3840
  LIMIT_FILTER_IMPORTS,
3377
3841
  LIMIT_FILTER_PROVIDER,
3378
3842
  MANIFEST_ACCESS_PORT_TOKEN,
@@ -3386,17 +3850,23 @@ UserCommands = _ts_decorate14([
3386
3850
  MfaSetupFlow,
3387
3851
  PLATFORM_DOCTOR_CHECK_PROVIDERS,
3388
3852
  PlanCatalogDoctorCheck,
3853
+ SCANNED_FOR_DB_CATALOG,
3854
+ SCANNED_FOR_MOVED_SETTINGS,
3855
+ SETTINGS_THAT_MOVED,
3389
3856
  UI_VUE_SPECIFIER,
3390
3857
  USER_MANAGEMENT_PORT_TOKEN,
3391
3858
  USER_PORT_TOKEN,
3392
3859
  UserCommands,
3393
3860
  UserPortDoctorCheck,
3861
+ WHERE_DB_CATALOG_GOES,
3862
+ WHERE_IT_GOES,
3394
3863
  WhoAmIFlow,
3864
+ appKeyPattern,
3395
3865
  appendConstraints,
3396
3866
  applyFragmentBlocks,
3397
3867
  applyTokens,
3398
3868
  assertModelsExist,
3399
- assertValidProjectKey,
3869
+ assertValidAppKey,
3400
3870
  assertValidQuotaKey,
3401
3871
  blankStringLiterals,
3402
3872
  blockBodyLines,
@@ -3404,6 +3874,7 @@ UserCommands = _ts_decorate14([
3404
3874
  buildImportMap,
3405
3875
  checkSchema,
3406
3876
  constraintsFor,
3877
+ describeDbCatalogOccurrence,
3407
3878
  enableFkPointers,
3408
3879
  extractBlockNames,
3409
3880
  extractBlocks,
@@ -3412,7 +3883,9 @@ UserCommands = _ts_decorate14([
3412
3883
  extractFragmentBlocks,
3413
3884
  extractModelBlocks,
3414
3885
  extractModelNames,
3886
+ findDbCatalogBlocks,
3415
3887
  findFkPointers,
3888
+ findMovedSettings,
3416
3889
  foreignKeyOf,
3417
3890
  hasBackRelation,
3418
3891
  hasConstraints,
@@ -3422,6 +3895,7 @@ UserCommands = _ts_decorate14([
3422
3895
  kebabCase,
3423
3896
  migrationCreatedBy,
3424
3897
  minimumQuotasPerPlan,
3898
+ modelsKeptPastTheTenant,
3425
3899
  namedImports,
3426
3900
  parseBlockAttributes,
3427
3901
  parseEnumValues,
@@ -3432,15 +3906,16 @@ UserCommands = _ts_decorate14([
3432
3906
  patchAppModule,
3433
3907
  patchOptionsFor,
3434
3908
  planInit,
3435
- projectKeyPattern,
3436
3909
  quotaKeyPattern,
3437
3910
  readEffectiveModuleResolution,
3438
3911
  relationNameOf,
3912
+ removeProjectKey,
3439
3913
  reportConstraints,
3440
3914
  rewriteImports,
3441
3915
  rewriteManifest,
3442
3916
  rewriteNames,
3443
3917
  rewriteSubpath,
3918
+ settingsWrittenTo,
3444
3919
  stripLineComment,
3445
3920
  structuralOnly,
3446
3921
  tablesAddressedBy