@saasicat/cli 1.0.0-rc.1 → 1.0.0-rc.10

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.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { MfaService, AdminAuditService, AdminManifestService, DiscoverySnapshot, ProviderSpec, DiscoveryScanner } from '@saasicat/nest';
2
2
  import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/core';
3
3
  import { Type, DynamicModule } from '@nestjs/common';
4
+ import * as TypeScript from 'typescript';
4
5
  import { CommandRunner } from 'nest-commander';
5
6
 
6
7
  declare const CLI_CONTEXT_CONFIG_TOKEN: unique symbol;
@@ -381,19 +382,41 @@ declare function extractModelNames(schema: string): string[];
381
382
  * `name -> complete block text incl. opening/closing braces`.
382
383
  */
383
384
  declare function extractModelBlocks(fragment: string): Map<string, string>;
385
+ /** Returns all `enum X { ... }` blocks from a fragment, keyed by name. */
386
+ declare function extractEnumBlocks(fragment: string): Map<string, string>;
387
+ /** Names of all top-level `enum X { ... }` blocks in the schema. */
388
+ declare function extractEnumNames(schema: string): string[];
389
+ /**
390
+ * What a fragment contributes: its enums and its models. Both travel
391
+ * together, because a model's `BillingCycle` field is a validation error in
392
+ * a schema that has the model and not the enum — which is what `apply` used
393
+ * to produce for every consumer whose schema did not already carry the enums
394
+ * by hand, and what the example app masked by carrying them.
395
+ */
396
+ interface FragmentBlocks {
397
+ enums: Map<string, string>;
398
+ models: Map<string, string>;
399
+ }
400
+ declare function extractFragmentBlocks(fragment: string): FragmentBlocks;
384
401
  interface ApplyResult {
385
- /** Number of models that were added. */
402
+ /** Models that were added. */
386
403
  added: string[];
387
- /** Number of models that were already present (no write). */
404
+ /** Models that were already present (no write). */
388
405
  skipped: string[];
389
- /** Resulting schema text. When `added.length === 0` identical to the input. */
406
+ /** Enums that were added, before the models that use them. */
407
+ addedEnums: string[];
408
+ /** Enums that were already present (no write). */
409
+ skippedEnums: string[];
410
+ /** Resulting schema text. When nothing was added, identical to the input. */
390
411
  schema: string;
391
412
  }
392
413
  /**
393
- * Appends missing models from `fragmentBlocks` to the end of `schema`. Existing
394
- * models (same name) stay unchanged and are listed in `skipped`.
414
+ * Appends missing enums and models from `fragmentBlocks` to the end of
415
+ * `schema`. Existing blocks (same name) stay unchanged and are listed as
416
+ * skipped. A plain map of models is accepted for the callers that only ever
417
+ * had models; the enums are then `[]`, which is what they were before.
395
418
  */
396
- declare function applyFragmentBlocks(schema: string, fragmentBlocks: Map<string, string>, options?: {
419
+ declare function applyFragmentBlocks(schema: string, fragmentBlocks: FragmentBlocks | Map<string, string>, options?: {
397
420
  fragmentLabel?: string;
398
421
  }): ApplyResult;
399
422
 
@@ -696,9 +719,9 @@ declare function assertModelsExist(declaredModels: readonly string[], models: Fk
696
719
 
697
720
  /** What the caller asked for. */
698
721
  interface InitOptions {
699
- /** The catalogue this app administers. Also the storage-key prefix. */
700
- projectKey: string;
701
- /** Human name in the manifest and the YAML. Defaults to `projectKey`. */
722
+ /** Slug of the application: npm package name, storage-key prefix, id prefix. */
723
+ appKey: string;
724
+ /** Human name in the manifest and the YAML. Defaults to `appKey`. */
702
725
  appName?: string;
703
726
  /** Admin API prefix, e.g. `/api/v1/admin`. */
704
727
  apiBase?: string;
@@ -804,8 +827,34 @@ interface PatchOptionsFromPlan {
804
827
  };
805
828
  }
806
829
 
807
- /** The pattern `plan-catalog.schema.json` puts on `projectKey`. */
808
- declare function projectKeyPattern(): RegExp;
830
+ interface ModuleResolutionVerdict {
831
+ readonly ok: boolean;
832
+ /** The effective setting, lower-cased as TypeScript names it, or null when unset. */
833
+ readonly value: string | null;
834
+ readonly reason?: string;
835
+ }
836
+ /**
837
+ * Judges an effective `moduleResolution`, as `readEffectiveModuleResolution`
838
+ * reports it.
839
+ *
840
+ * Unset is `ok`: TypeScript then derives it from `module`, and a `module`
841
+ * that implies the old resolution is a setup this cannot see from here —
842
+ * the next build says so, with TypeScript's own message.
843
+ */
844
+ declare function judgeModuleResolution(value: string | null): ModuleResolutionVerdict;
845
+ /**
846
+ * The `moduleResolution` a project's `tsconfig.json` resolves to, after
847
+ * `extends`, lower-cased as TypeScript names the kind (`node10`, `node16`,
848
+ * `nodenext`, `bundler`, `classic`) — or null when there is no config, the
849
+ * config cannot be parsed, or the option is not set anywhere in the chain.
850
+ *
851
+ * `ts` is whichever TypeScript will compile the project: the consumer's own
852
+ * when it can be resolved from the project root, so the reading matches the
853
+ * build that follows.
854
+ */
855
+ declare function readEffectiveModuleResolution(root: string, ts: typeof TypeScript): string | null;
856
+
857
+ declare function appKeyPattern(): RegExp;
809
858
  /** The pattern it puts on the keys inside a plan's `quotas` object. */
810
859
  declare function quotaKeyPattern(): RegExp;
811
860
  /** How many quotas a plan must declare — 1 today, and read rather than assumed. */
@@ -816,10 +865,25 @@ declare function minimumQuotasPerPlan(): number;
816
865
  * The message carries the pattern rather than a prose paraphrase, because the
817
866
  * paraphrase is what goes stale.
818
867
  */
819
- declare function assertValidProjectKey(projectKey: string): void;
868
+ declare function assertValidAppKey(appKey: string): void;
820
869
  /** The same, for a quota key. `additionalProperties: false` makes it a hard rule. */
821
870
  declare function assertValidQuotaKey(quotaKey: string): void;
822
871
 
872
+ interface WrittenSetting {
873
+ /** Dotted path as it reads in the file, e.g. `tenantBilling.cancellationNoticeDays.monthly`. */
874
+ key: string;
875
+ /** The value as JSON — `0`, `[]`, `"EUR"` — so an empty list is visible as one. */
876
+ value: string;
877
+ }
878
+ /**
879
+ * The settings a generated `config/saas.yaml` carries.
880
+ *
881
+ * Loaded with the platform's own loader, not a YAML parse: a document `init`
882
+ * writes that the platform would refuse is a bug worth failing the generation
883
+ * for, rather than one the first boot reports after every file exists.
884
+ */
885
+ declare function settingsWrittenTo(catalogYaml: string, source?: string): WrittenSetting[];
886
+
823
887
  /** One entry of the move table: where a file was, and where it went. */
824
888
  interface MoveTable {
825
889
  readonly moves: Readonly<Record<string, string>>;
@@ -931,6 +995,147 @@ interface ManifestRewriteOptions {
931
995
  }
932
996
  declare function rewriteManifest(text: string, table: RenameTable, options: ManifestRewriteOptions): RenameResult;
933
997
 
998
+ interface ProjectKeyResult {
999
+ readonly text: string;
1000
+ /** How many occurrences were taken out. */
1001
+ readonly rewritten: number;
1002
+ /**
1003
+ * 1-based line numbers of the occurrences left in place.
1004
+ *
1005
+ * Reported rather than removed: the codemod could not tell them from a
1006
+ * consumer's own field, and a wrong deletion is worse than a named one.
1007
+ */
1008
+ readonly undecided: readonly number[];
1009
+ }
1010
+ /**
1011
+ * Rewrites one source file.
1012
+ *
1013
+ * `yaml` switches to the config form: there the field is a top-level key in a
1014
+ * file the platform owns the schema of, so it is decidable without an anchor.
1015
+ */
1016
+ declare function removeProjectKey(text: string, kind?: 'source' | 'yaml'): ProjectKeyResult;
1017
+
1018
+ /**
1019
+ * The settings that belong in `config/saas.yaml#tenantBilling`, read off the
1020
+ * schema that defines them.
1021
+ *
1022
+ * Not a list here, and not a copy of the one in `@saasicat/nest`: both derive
1023
+ * from the same schema, so the day a third setting moves into that block, this
1024
+ * codemod names it and the module refuses it without either being edited.
1025
+ */
1026
+ declare const SETTINGS_THAT_MOVED: readonly string[];
1027
+ type MovedSetting = string;
1028
+ interface MovedSettingOccurrence {
1029
+ /** Which setting it is, so the report can say where it goes. */
1030
+ readonly setting: MovedSetting;
1031
+ /** 1-based line number. */
1032
+ readonly line: number;
1033
+ }
1034
+ interface MovedSettingsResult {
1035
+ readonly occurrences: readonly MovedSettingOccurrence[];
1036
+ }
1037
+ /**
1038
+ * Files a moved setting can actually be passed in.
1039
+ *
1040
+ * The codemod walk includes Markdown, and both consumers keep large
1041
+ * documentation folders — an upgrade note that mentions `cancellationNoticeDays`
1042
+ * would land in the report beside the line somebody has to change, and a report
1043
+ * that mixes the two is one nobody reads twice. Prose cannot pass a module
1044
+ * option, so prose is not scanned.
1045
+ */
1046
+ declare const SCANNED_FOR_MOVED_SETTINGS: RegExp;
1047
+ /**
1048
+ * Every occurrence of a moved setting in one source file.
1049
+ *
1050
+ * Matched on word boundaries alone: a longer name that contains it
1051
+ * (`cancellationNoticeDaysV2`) is not reported, and everything else is —
1052
+ * including a mention in a comment or a string.
1053
+ *
1054
+ * That last part is deliberate, and it is the opposite trade from the one this
1055
+ * comment used to claim. Requiring a colon would read as "only a property",
1056
+ * and it would then miss `{ cancellationNoticeDays }` and
1057
+ * `const { cancellationNoticeDays } = options` — two ordinary ways to pass the
1058
+ * same option. Telling a comment from code needs the grammar, which this does
1059
+ * not have. So it over-reports inside code, where the cost is a glance, rather
1060
+ * than under-reporting, where the cost is somebody not learning that their
1061
+ * value is about to stop being read.
1062
+ *
1063
+ * A property access (`config.cancellationNoticeDays`) is reported too: reading
1064
+ * the value back from module options is the same migration, one step further
1065
+ * along.
1066
+ */
1067
+ declare function findMovedSettings(text: string): MovedSettingsResult;
1068
+ /**
1069
+ * Where a setting goes, for the report.
1070
+ *
1071
+ * One sentence per setting rather than one for both: they end up in the same
1072
+ * block and mean different things, and "move these two to the file" is the
1073
+ * instruction people follow halfway.
1074
+ */
1075
+ declare const WHERE_IT_GOES: Record<string, string>;
1076
+
1077
+ /** Which files a `dbCatalog` can be passed in: code, not prose. */
1078
+ declare const SCANNED_FOR_DB_CATALOG: RegExp;
1079
+ type DbCatalogShape = 'values' | 'mixed' | 'reference';
1080
+ interface DbCatalogOccurrence {
1081
+ /** 1-based line of the `dbCatalog:` property. */
1082
+ readonly line: number;
1083
+ /**
1084
+ * `values` — an object literal with no `path`: the old shape whole.
1085
+ * `mixed` — a `path` with something beside it that the option does not
1086
+ * take: an upgrade that stopped halfway, and the platform refuses it too.
1087
+ * `reference` — anything else on the right of the colon: a variable, a
1088
+ * call, a spread. This cannot see what it carries, so it is named for a
1089
+ * person to look at rather than passed over.
1090
+ */
1091
+ readonly shape: DbCatalogShape;
1092
+ /**
1093
+ * The members that are not what the option takes — every one of a
1094
+ * `values` block, the ones left beside the path of a `mixed` one, and
1095
+ * nothing for a `reference`. A spread is listed as `...name`, because
1096
+ * what it carries is decided elsewhere.
1097
+ */
1098
+ readonly leftovers: readonly string[];
1099
+ }
1100
+ interface DbCatalogResult {
1101
+ readonly occurrences: readonly DbCatalogOccurrence[];
1102
+ }
1103
+ /**
1104
+ * Every `dbCatalog:` property in one source file, with what stands to its right.
1105
+ *
1106
+ * A property in code, which means the name followed by a colon, outside any
1107
+ * comment or string. The other codemod in this family reports every
1108
+ * word-boundary mention of a setting, including one in a comment, on the
1109
+ * reasoning that over-reporting inside code costs a glance. That reasoning
1110
+ * does not carry here: `saasicat init` writes the sentence "pass `dbCatalog`
1111
+ * instead" into every generated `app.module.ts`, so a mention is the normal
1112
+ * case and a report of it would be noise on every upgrade — and a block
1113
+ * commented out, or quoted as a sample, is migration work that does not
1114
+ * exist. An OPTIONAL type member (`dbCatalog?:`) is not a property either: the
1115
+ * `?` stands where the colon would. A required one (`dbCatalog: DbCatalogOptions`)
1116
+ * is the same tokens as a value passed in from elsewhere, and is reported as a
1117
+ * `reference` for that reason — a glance, which is what `reference` is for. A
1118
+ * shorthand `{ dbCatalog }` is not seen — the value it carries is elsewhere,
1119
+ * and the module's refusal names it at boot.
1120
+ *
1121
+ * What counts as migrated is read off `DB_CATALOG_MEMBERS`, the list the
1122
+ * platform's own refusal reads, so the two cannot disagree about a block.
1123
+ */
1124
+ declare function findDbCatalogBlocks(text: string): DbCatalogResult;
1125
+ /**
1126
+ * What is wrong with one occurrence, in the words the report uses.
1127
+ *
1128
+ * Beside the shapes rather than in the printer, because one of them has no
1129
+ * leftovers to name: a `values` block whose only member is one the option
1130
+ * takes — `dbCatalog: { env: process.env }` — or one emptied mid-edit is
1131
+ * genuinely broken and genuinely refused at boot, but "carries the values:"
1132
+ * with nothing after it names a file to go and look at without saying what to
1133
+ * look for.
1134
+ */
1135
+ declare function describeDbCatalogOccurrence(occurrence: Pick<DbCatalogOccurrence, 'shape' | 'leftovers'>): string;
1136
+ /** What to write instead, for the report. */
1137
+ declare const WHERE_DB_CATALOG_GOES: string;
1138
+
934
1139
  interface PatchAppModuleOptions {
935
1140
  /** Import specifier for the persistence bundle, or null when not generated. */
936
1141
  persistenceImport: string | null;
@@ -1174,4 +1379,4 @@ declare class UserCommands extends CommandRunner {
1174
1379
  parsePassword(val: string): string;
1175
1380
  }
1176
1381
 
1177
- export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, type BlockAttributeKind, type BlockAttributes, CLI_CONTEXT_CONFIG_TOKEN, CONSTRAINTS_MARKER, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, type ConstraintsOutcome, type ConstraintsReport, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type EnableFkResult, type FieldMismatch, type FieldMismatchReason, type FieldSignature, type FkModelNames, type FkPointer, type InitOptions, type InitPlan, LIMIT_FILTER_IMPORTS, LIMIT_FILTER_PROVIDER, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, type ManifestRewriteOptions, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, type MoveTable, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type QuotaProviderFile, type QuotaSpec, type RenameResult, type RenameTable, type RewriteResult, type SchemaCheckReport, UI_VUE_SPECIFIER, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidProjectKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, buildImportMap, checkSchema, constraintsFor, enableFkPointers, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, findFkPointers, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, projectKeyPattern, quotaKeyPattern, relationNameOf, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
1382
+ export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, type BlockAttributeKind, type BlockAttributes, CLI_CONTEXT_CONFIG_TOKEN, CONSTRAINTS_MARKER, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, type ConstraintsOutcome, type ConstraintsReport, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, type DbCatalogOccurrence, type DbCatalogResult, type DbCatalogShape, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type EnableFkResult, type FieldMismatch, type FieldMismatchReason, type FieldSignature, type FkModelNames, type FkPointer, type FragmentBlocks, type InitOptions, type InitPlan, LIMIT_FILTER_IMPORTS, LIMIT_FILTER_PROVIDER, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, type ManifestRewriteOptions, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, type ModuleResolutionVerdict, type MoveTable, type MovedSetting, type MovedSettingOccurrence, type MovedSettingsResult, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type ProjectKeyResult, type QuotaProviderFile, type QuotaSpec, type RenameResult, type RenameTable, type RewriteResult, SCANNED_FOR_DB_CATALOG, SCANNED_FOR_MOVED_SETTINGS, SETTINGS_THAT_MOVED, type SchemaCheckReport, UI_VUE_SPECIFIER, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WHERE_DB_CATALOG_GOES, WHERE_IT_GOES, WhoAmIFlow, type WhoAmIResult, type WrittenSetting, appKeyPattern, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidAppKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, buildImportMap, checkSchema, constraintsFor, describeDbCatalogOccurrence, enableFkPointers, extractBlockNames, extractBlocks, extractEnumBlocks, extractEnumNames, extractFragmentBlocks, extractModelBlocks, extractModelNames, findDbCatalogBlocks, findFkPointers, findMovedSettings, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, judgeModuleResolution, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, removeProjectKey, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, settingsWrittenTo, stripLineComment, structuralOnly, tablesAddressedBy };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { MfaService, AdminAuditService, AdminManifestService, DiscoverySnapshot, ProviderSpec, DiscoveryScanner } from '@saasicat/nest';
2
2
  import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/core';
3
3
  import { Type, DynamicModule } from '@nestjs/common';
4
+ import * as TypeScript from 'typescript';
4
5
  import { CommandRunner } from 'nest-commander';
5
6
 
6
7
  declare const CLI_CONTEXT_CONFIG_TOKEN: unique symbol;
@@ -381,19 +382,41 @@ declare function extractModelNames(schema: string): string[];
381
382
  * `name -> complete block text incl. opening/closing braces`.
382
383
  */
383
384
  declare function extractModelBlocks(fragment: string): Map<string, string>;
385
+ /** Returns all `enum X { ... }` blocks from a fragment, keyed by name. */
386
+ declare function extractEnumBlocks(fragment: string): Map<string, string>;
387
+ /** Names of all top-level `enum X { ... }` blocks in the schema. */
388
+ declare function extractEnumNames(schema: string): string[];
389
+ /**
390
+ * What a fragment contributes: its enums and its models. Both travel
391
+ * together, because a model's `BillingCycle` field is a validation error in
392
+ * a schema that has the model and not the enum — which is what `apply` used
393
+ * to produce for every consumer whose schema did not already carry the enums
394
+ * by hand, and what the example app masked by carrying them.
395
+ */
396
+ interface FragmentBlocks {
397
+ enums: Map<string, string>;
398
+ models: Map<string, string>;
399
+ }
400
+ declare function extractFragmentBlocks(fragment: string): FragmentBlocks;
384
401
  interface ApplyResult {
385
- /** Number of models that were added. */
402
+ /** Models that were added. */
386
403
  added: string[];
387
- /** Number of models that were already present (no write). */
404
+ /** Models that were already present (no write). */
388
405
  skipped: string[];
389
- /** Resulting schema text. When `added.length === 0` identical to the input. */
406
+ /** Enums that were added, before the models that use them. */
407
+ addedEnums: string[];
408
+ /** Enums that were already present (no write). */
409
+ skippedEnums: string[];
410
+ /** Resulting schema text. When nothing was added, identical to the input. */
390
411
  schema: string;
391
412
  }
392
413
  /**
393
- * Appends missing models from `fragmentBlocks` to the end of `schema`. Existing
394
- * models (same name) stay unchanged and are listed in `skipped`.
414
+ * Appends missing enums and models from `fragmentBlocks` to the end of
415
+ * `schema`. Existing blocks (same name) stay unchanged and are listed as
416
+ * skipped. A plain map of models is accepted for the callers that only ever
417
+ * had models; the enums are then `[]`, which is what they were before.
395
418
  */
396
- declare function applyFragmentBlocks(schema: string, fragmentBlocks: Map<string, string>, options?: {
419
+ declare function applyFragmentBlocks(schema: string, fragmentBlocks: FragmentBlocks | Map<string, string>, options?: {
397
420
  fragmentLabel?: string;
398
421
  }): ApplyResult;
399
422
 
@@ -696,9 +719,9 @@ declare function assertModelsExist(declaredModels: readonly string[], models: Fk
696
719
 
697
720
  /** What the caller asked for. */
698
721
  interface InitOptions {
699
- /** The catalogue this app administers. Also the storage-key prefix. */
700
- projectKey: string;
701
- /** Human name in the manifest and the YAML. Defaults to `projectKey`. */
722
+ /** Slug of the application: npm package name, storage-key prefix, id prefix. */
723
+ appKey: string;
724
+ /** Human name in the manifest and the YAML. Defaults to `appKey`. */
702
725
  appName?: string;
703
726
  /** Admin API prefix, e.g. `/api/v1/admin`. */
704
727
  apiBase?: string;
@@ -804,8 +827,34 @@ interface PatchOptionsFromPlan {
804
827
  };
805
828
  }
806
829
 
807
- /** The pattern `plan-catalog.schema.json` puts on `projectKey`. */
808
- declare function projectKeyPattern(): RegExp;
830
+ interface ModuleResolutionVerdict {
831
+ readonly ok: boolean;
832
+ /** The effective setting, lower-cased as TypeScript names it, or null when unset. */
833
+ readonly value: string | null;
834
+ readonly reason?: string;
835
+ }
836
+ /**
837
+ * Judges an effective `moduleResolution`, as `readEffectiveModuleResolution`
838
+ * reports it.
839
+ *
840
+ * Unset is `ok`: TypeScript then derives it from `module`, and a `module`
841
+ * that implies the old resolution is a setup this cannot see from here —
842
+ * the next build says so, with TypeScript's own message.
843
+ */
844
+ declare function judgeModuleResolution(value: string | null): ModuleResolutionVerdict;
845
+ /**
846
+ * The `moduleResolution` a project's `tsconfig.json` resolves to, after
847
+ * `extends`, lower-cased as TypeScript names the kind (`node10`, `node16`,
848
+ * `nodenext`, `bundler`, `classic`) — or null when there is no config, the
849
+ * config cannot be parsed, or the option is not set anywhere in the chain.
850
+ *
851
+ * `ts` is whichever TypeScript will compile the project: the consumer's own
852
+ * when it can be resolved from the project root, so the reading matches the
853
+ * build that follows.
854
+ */
855
+ declare function readEffectiveModuleResolution(root: string, ts: typeof TypeScript): string | null;
856
+
857
+ declare function appKeyPattern(): RegExp;
809
858
  /** The pattern it puts on the keys inside a plan's `quotas` object. */
810
859
  declare function quotaKeyPattern(): RegExp;
811
860
  /** How many quotas a plan must declare — 1 today, and read rather than assumed. */
@@ -816,10 +865,25 @@ declare function minimumQuotasPerPlan(): number;
816
865
  * The message carries the pattern rather than a prose paraphrase, because the
817
866
  * paraphrase is what goes stale.
818
867
  */
819
- declare function assertValidProjectKey(projectKey: string): void;
868
+ declare function assertValidAppKey(appKey: string): void;
820
869
  /** The same, for a quota key. `additionalProperties: false` makes it a hard rule. */
821
870
  declare function assertValidQuotaKey(quotaKey: string): void;
822
871
 
872
+ interface WrittenSetting {
873
+ /** Dotted path as it reads in the file, e.g. `tenantBilling.cancellationNoticeDays.monthly`. */
874
+ key: string;
875
+ /** The value as JSON — `0`, `[]`, `"EUR"` — so an empty list is visible as one. */
876
+ value: string;
877
+ }
878
+ /**
879
+ * The settings a generated `config/saas.yaml` carries.
880
+ *
881
+ * Loaded with the platform's own loader, not a YAML parse: a document `init`
882
+ * writes that the platform would refuse is a bug worth failing the generation
883
+ * for, rather than one the first boot reports after every file exists.
884
+ */
885
+ declare function settingsWrittenTo(catalogYaml: string, source?: string): WrittenSetting[];
886
+
823
887
  /** One entry of the move table: where a file was, and where it went. */
824
888
  interface MoveTable {
825
889
  readonly moves: Readonly<Record<string, string>>;
@@ -931,6 +995,147 @@ interface ManifestRewriteOptions {
931
995
  }
932
996
  declare function rewriteManifest(text: string, table: RenameTable, options: ManifestRewriteOptions): RenameResult;
933
997
 
998
+ interface ProjectKeyResult {
999
+ readonly text: string;
1000
+ /** How many occurrences were taken out. */
1001
+ readonly rewritten: number;
1002
+ /**
1003
+ * 1-based line numbers of the occurrences left in place.
1004
+ *
1005
+ * Reported rather than removed: the codemod could not tell them from a
1006
+ * consumer's own field, and a wrong deletion is worse than a named one.
1007
+ */
1008
+ readonly undecided: readonly number[];
1009
+ }
1010
+ /**
1011
+ * Rewrites one source file.
1012
+ *
1013
+ * `yaml` switches to the config form: there the field is a top-level key in a
1014
+ * file the platform owns the schema of, so it is decidable without an anchor.
1015
+ */
1016
+ declare function removeProjectKey(text: string, kind?: 'source' | 'yaml'): ProjectKeyResult;
1017
+
1018
+ /**
1019
+ * The settings that belong in `config/saas.yaml#tenantBilling`, read off the
1020
+ * schema that defines them.
1021
+ *
1022
+ * Not a list here, and not a copy of the one in `@saasicat/nest`: both derive
1023
+ * from the same schema, so the day a third setting moves into that block, this
1024
+ * codemod names it and the module refuses it without either being edited.
1025
+ */
1026
+ declare const SETTINGS_THAT_MOVED: readonly string[];
1027
+ type MovedSetting = string;
1028
+ interface MovedSettingOccurrence {
1029
+ /** Which setting it is, so the report can say where it goes. */
1030
+ readonly setting: MovedSetting;
1031
+ /** 1-based line number. */
1032
+ readonly line: number;
1033
+ }
1034
+ interface MovedSettingsResult {
1035
+ readonly occurrences: readonly MovedSettingOccurrence[];
1036
+ }
1037
+ /**
1038
+ * Files a moved setting can actually be passed in.
1039
+ *
1040
+ * The codemod walk includes Markdown, and both consumers keep large
1041
+ * documentation folders — an upgrade note that mentions `cancellationNoticeDays`
1042
+ * would land in the report beside the line somebody has to change, and a report
1043
+ * that mixes the two is one nobody reads twice. Prose cannot pass a module
1044
+ * option, so prose is not scanned.
1045
+ */
1046
+ declare const SCANNED_FOR_MOVED_SETTINGS: RegExp;
1047
+ /**
1048
+ * Every occurrence of a moved setting in one source file.
1049
+ *
1050
+ * Matched on word boundaries alone: a longer name that contains it
1051
+ * (`cancellationNoticeDaysV2`) is not reported, and everything else is —
1052
+ * including a mention in a comment or a string.
1053
+ *
1054
+ * That last part is deliberate, and it is the opposite trade from the one this
1055
+ * comment used to claim. Requiring a colon would read as "only a property",
1056
+ * and it would then miss `{ cancellationNoticeDays }` and
1057
+ * `const { cancellationNoticeDays } = options` — two ordinary ways to pass the
1058
+ * same option. Telling a comment from code needs the grammar, which this does
1059
+ * not have. So it over-reports inside code, where the cost is a glance, rather
1060
+ * than under-reporting, where the cost is somebody not learning that their
1061
+ * value is about to stop being read.
1062
+ *
1063
+ * A property access (`config.cancellationNoticeDays`) is reported too: reading
1064
+ * the value back from module options is the same migration, one step further
1065
+ * along.
1066
+ */
1067
+ declare function findMovedSettings(text: string): MovedSettingsResult;
1068
+ /**
1069
+ * Where a setting goes, for the report.
1070
+ *
1071
+ * One sentence per setting rather than one for both: they end up in the same
1072
+ * block and mean different things, and "move these two to the file" is the
1073
+ * instruction people follow halfway.
1074
+ */
1075
+ declare const WHERE_IT_GOES: Record<string, string>;
1076
+
1077
+ /** Which files a `dbCatalog` can be passed in: code, not prose. */
1078
+ declare const SCANNED_FOR_DB_CATALOG: RegExp;
1079
+ type DbCatalogShape = 'values' | 'mixed' | 'reference';
1080
+ interface DbCatalogOccurrence {
1081
+ /** 1-based line of the `dbCatalog:` property. */
1082
+ readonly line: number;
1083
+ /**
1084
+ * `values` — an object literal with no `path`: the old shape whole.
1085
+ * `mixed` — a `path` with something beside it that the option does not
1086
+ * take: an upgrade that stopped halfway, and the platform refuses it too.
1087
+ * `reference` — anything else on the right of the colon: a variable, a
1088
+ * call, a spread. This cannot see what it carries, so it is named for a
1089
+ * person to look at rather than passed over.
1090
+ */
1091
+ readonly shape: DbCatalogShape;
1092
+ /**
1093
+ * The members that are not what the option takes — every one of a
1094
+ * `values` block, the ones left beside the path of a `mixed` one, and
1095
+ * nothing for a `reference`. A spread is listed as `...name`, because
1096
+ * what it carries is decided elsewhere.
1097
+ */
1098
+ readonly leftovers: readonly string[];
1099
+ }
1100
+ interface DbCatalogResult {
1101
+ readonly occurrences: readonly DbCatalogOccurrence[];
1102
+ }
1103
+ /**
1104
+ * Every `dbCatalog:` property in one source file, with what stands to its right.
1105
+ *
1106
+ * A property in code, which means the name followed by a colon, outside any
1107
+ * comment or string. The other codemod in this family reports every
1108
+ * word-boundary mention of a setting, including one in a comment, on the
1109
+ * reasoning that over-reporting inside code costs a glance. That reasoning
1110
+ * does not carry here: `saasicat init` writes the sentence "pass `dbCatalog`
1111
+ * instead" into every generated `app.module.ts`, so a mention is the normal
1112
+ * case and a report of it would be noise on every upgrade — and a block
1113
+ * commented out, or quoted as a sample, is migration work that does not
1114
+ * exist. An OPTIONAL type member (`dbCatalog?:`) is not a property either: the
1115
+ * `?` stands where the colon would. A required one (`dbCatalog: DbCatalogOptions`)
1116
+ * is the same tokens as a value passed in from elsewhere, and is reported as a
1117
+ * `reference` for that reason — a glance, which is what `reference` is for. A
1118
+ * shorthand `{ dbCatalog }` is not seen — the value it carries is elsewhere,
1119
+ * and the module's refusal names it at boot.
1120
+ *
1121
+ * What counts as migrated is read off `DB_CATALOG_MEMBERS`, the list the
1122
+ * platform's own refusal reads, so the two cannot disagree about a block.
1123
+ */
1124
+ declare function findDbCatalogBlocks(text: string): DbCatalogResult;
1125
+ /**
1126
+ * What is wrong with one occurrence, in the words the report uses.
1127
+ *
1128
+ * Beside the shapes rather than in the printer, because one of them has no
1129
+ * leftovers to name: a `values` block whose only member is one the option
1130
+ * takes — `dbCatalog: { env: process.env }` — or one emptied mid-edit is
1131
+ * genuinely broken and genuinely refused at boot, but "carries the values:"
1132
+ * with nothing after it names a file to go and look at without saying what to
1133
+ * look for.
1134
+ */
1135
+ declare function describeDbCatalogOccurrence(occurrence: Pick<DbCatalogOccurrence, 'shape' | 'leftovers'>): string;
1136
+ /** What to write instead, for the report. */
1137
+ declare const WHERE_DB_CATALOG_GOES: string;
1138
+
934
1139
  interface PatchAppModuleOptions {
935
1140
  /** Import specifier for the persistence bundle, or null when not generated. */
936
1141
  persistenceImport: string | null;
@@ -1174,4 +1379,4 @@ declare class UserCommands extends CommandRunner {
1174
1379
  parsePassword(val: string): string;
1175
1380
  }
1176
1381
 
1177
- export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, type BlockAttributeKind, type BlockAttributes, CLI_CONTEXT_CONFIG_TOKEN, CONSTRAINTS_MARKER, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, type ConstraintsOutcome, type ConstraintsReport, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type EnableFkResult, type FieldMismatch, type FieldMismatchReason, type FieldSignature, type FkModelNames, type FkPointer, type InitOptions, type InitPlan, LIMIT_FILTER_IMPORTS, LIMIT_FILTER_PROVIDER, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, type ManifestRewriteOptions, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, type MoveTable, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type QuotaProviderFile, type QuotaSpec, type RenameResult, type RenameTable, type RewriteResult, type SchemaCheckReport, UI_VUE_SPECIFIER, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidProjectKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, buildImportMap, checkSchema, constraintsFor, enableFkPointers, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, findFkPointers, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, projectKeyPattern, quotaKeyPattern, relationNameOf, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
1382
+ export { AUDIT_QUERY_PORT_TOKEN, AdminCommands, AdminManifestDoctorCheck, AdminMfaSetupCommand, AdminWhoamiCommand, type ApplyResult, AuditCommands, AuditTailCommand, AuditTailFlow, type AuditTailOptions, type BlockAttributeKind, type BlockAttributes, CLI_CONTEXT_CONFIG_TOKEN, CONSTRAINTS_MARKER, type CheckSeverity, type CliContextConfig, CliContextModule, type CliContextModuleOptions, CliContextService, CliError, type CliIdentity, type ConstraintsOutcome, type ConstraintsReport, DEFAULT_MANIFEST_CHECKS, DOCTOR_CHECKS_TOKEN, type DbCatalogOccurrence, type DbCatalogResult, type DbCatalogShape, DiscoveryCommands, DiscoveryScanCommand, DiscoverySnapshotDoctorCheck, type DoctorCheck, type DoctorCheckResult, DoctorCommands, DoctorFlow, type DoctorReport, type EnableFkResult, type FieldMismatch, type FieldMismatchReason, type FieldSignature, type FkModelNames, type FkPointer, type FragmentBlocks, type InitOptions, type InitPlan, LIMIT_FILTER_IMPORTS, LIMIT_FILTER_PROVIDER, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, type ManifestRewriteOptions, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, type ModuleResolutionVerdict, type MoveTable, type MovedSetting, type MovedSettingOccurrence, type MovedSettingsResult, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type ProjectKeyResult, type QuotaProviderFile, type QuotaSpec, type RenameResult, type RenameTable, type RewriteResult, SCANNED_FOR_DB_CATALOG, SCANNED_FOR_MOVED_SETTINGS, SETTINGS_THAT_MOVED, type SchemaCheckReport, UI_VUE_SPECIFIER, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WHERE_DB_CATALOG_GOES, WHERE_IT_GOES, WhoAmIFlow, type WhoAmIResult, type WrittenSetting, appKeyPattern, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidAppKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, buildImportMap, checkSchema, constraintsFor, describeDbCatalogOccurrence, enableFkPointers, extractBlockNames, extractBlocks, extractEnumBlocks, extractEnumNames, extractFragmentBlocks, extractModelBlocks, extractModelNames, findDbCatalogBlocks, findFkPointers, findMovedSettings, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, judgeModuleResolution, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, removeProjectKey, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, settingsWrittenTo, stripLineComment, structuralOnly, tablesAddressedBy };