@saasicat/cli 0.27.0 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -64,6 +64,7 @@ __export(index_exports, {
64
64
  MfaSetupFlow: () => MfaSetupFlow,
65
65
  PLATFORM_DOCTOR_CHECK_PROVIDERS: () => PLATFORM_DOCTOR_CHECK_PROVIDERS,
66
66
  PlanCatalogDoctorCheck: () => PlanCatalogDoctorCheck,
67
+ UI_VUE_SPECIFIER: () => UI_VUE_SPECIFIER,
67
68
  USER_MANAGEMENT_PORT_TOKEN: () => USER_MANAGEMENT_PORT_TOKEN,
68
69
  USER_PORT_TOKEN: () => USER_PORT_TOKEN,
69
70
  UserCommands: () => UserCommands,
@@ -78,6 +79,7 @@ __export(index_exports, {
78
79
  blankStringLiterals: () => blankStringLiterals,
79
80
  blockBodyLines: () => blockBodyLines,
80
81
  breaksContract: () => breaksContract,
82
+ buildImportMap: () => buildImportMap,
81
83
  checkSchema: () => checkSchema,
82
84
  constraintsFor: () => constraintsFor,
83
85
  enableFkPointers: () => enableFkPointers,
@@ -89,10 +91,12 @@ __export(index_exports, {
89
91
  foreignKeyOf: () => foreignKeyOf,
90
92
  hasBackRelation: () => hasBackRelation,
91
93
  hasConstraints: () => hasConstraints,
94
+ isNoLongerPublic: () => isNoLongerPublic,
92
95
  isOneToOne: () => isOneToOne,
93
96
  kebabCase: () => kebabCase,
94
97
  migrationCreatedBy: () => migrationCreatedBy,
95
98
  minimumQuotasPerPlan: () => minimumQuotasPerPlan,
99
+ namedImports: () => namedImports,
96
100
  parseBlockAttributes: () => parseBlockAttributes,
97
101
  parseEnumValues: () => parseEnumValues,
98
102
  parseFields: () => parseFields,
@@ -106,20 +110,24 @@ __export(index_exports, {
106
110
  quotaKeyPattern: () => quotaKeyPattern,
107
111
  relationNameOf: () => relationNameOf,
108
112
  reportConstraints: () => reportConstraints,
113
+ rewriteImports: () => rewriteImports,
114
+ rewriteManifest: () => rewriteManifest,
115
+ rewriteNames: () => rewriteNames,
116
+ rewriteSubpath: () => rewriteSubpath,
109
117
  stripLineComment: () => stripLineComment,
110
118
  structuralOnly: () => structuralOnly,
111
119
  tablesAddressedBy: () => tablesAddressedBy
112
120
  });
113
121
  module.exports = __toCommonJS(index_exports);
114
122
 
115
- // src/tokens.ts
116
- var CLI_CONTEXT_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/Config");
117
- var USER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/UserPort");
118
- var USER_MANAGEMENT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/UserManagementPort");
119
- var AUDIT_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/AuditQueryPort");
120
- var DOCTOR_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/DoctorChecks");
121
- var MANIFEST_ACCESS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/ManifestAccessPort");
122
- var MANIFEST_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/ManifestChecks");
123
+ // src/cli.tokens.ts
124
+ var CLI_CONTEXT_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/Config");
125
+ var USER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/UserPort");
126
+ var USER_MANAGEMENT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/UserManagementPort");
127
+ var AUDIT_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/AuditQueryPort");
128
+ var DOCTOR_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/DoctorChecks");
129
+ var MANIFEST_ACCESS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/ManifestAccessPort");
130
+ var MANIFEST_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/ManifestChecks");
123
131
 
124
132
  // src/cli-context.service.ts
125
133
  var os = __toESM(require("os"), 1);
@@ -1827,6 +1835,269 @@ function patchOptionsFor(plan) {
1827
1835
  }
1828
1836
  __name(patchOptionsFor, "patchOptionsFor");
1829
1837
 
1838
+ // src/codemods/v1-imports.ts
1839
+ var PUBLIC_PREFIXES = [
1840
+ "ui/",
1841
+ "layouts/",
1842
+ "auth/",
1843
+ "pages/"
1844
+ ];
1845
+ function buildImportMap(table) {
1846
+ const map = /* @__PURE__ */ new Map();
1847
+ for (const [from, to] of Object.entries(table.moves)) {
1848
+ const onSurface = PUBLIC_PREFIXES.some((prefix) => to.startsWith(prefix));
1849
+ if (!onSurface && !to.startsWith("@")) continue;
1850
+ if (from.startsWith("components/")) {
1851
+ map.set(from, to);
1852
+ continue;
1853
+ }
1854
+ if (!from.startsWith("pages-standard/")) continue;
1855
+ const file = from.slice("pages-standard/".length);
1856
+ if (file.includes("/")) continue;
1857
+ map.set(from, to);
1858
+ map.set(`pages/${file}`, to);
1859
+ }
1860
+ for (const [from, to] of Object.entries(table.packages ?? {})) {
1861
+ if (from === "_") continue;
1862
+ map.set(from, to);
1863
+ }
1864
+ for (const [from, to] of Object.entries(table.moveDirectories ?? {})) {
1865
+ map.set(`${from}/`, `${to}/`);
1866
+ }
1867
+ return map;
1868
+ }
1869
+ __name(buildImportMap, "buildImportMap");
1870
+ function wentPrivate(map, subpath) {
1871
+ for (const [prefix, target] of map) {
1872
+ if (prefix.endsWith("/") && target.startsWith("internal/") && subpath.startsWith(prefix)) {
1873
+ return true;
1874
+ }
1875
+ }
1876
+ return false;
1877
+ }
1878
+ __name(wentPrivate, "wentPrivate");
1879
+ function rewriteSubpath(map, subpath) {
1880
+ const direct = map.get(subpath);
1881
+ if (direct !== void 0 && direct !== subpath) return direct;
1882
+ for (const [prefix, target] of map) {
1883
+ if (prefix.endsWith("/") && target.startsWith("@") && subpath.startsWith(prefix)) {
1884
+ return `${target}${subpath.slice(prefix.length)}`;
1885
+ }
1886
+ }
1887
+ if (subpath.startsWith("pages-standard/") && !wentPrivate(map, subpath)) {
1888
+ const file = subpath.slice("pages-standard/".length);
1889
+ if (!file.includes("/")) return `pages/${file}`;
1890
+ }
1891
+ return null;
1892
+ }
1893
+ __name(rewriteSubpath, "rewriteSubpath");
1894
+ function isNoLongerPublic(map, subpath) {
1895
+ if (subpath.startsWith("components/")) return !map.has(subpath);
1896
+ return subpath.startsWith("pages-standard/") && wentPrivate(map, subpath);
1897
+ }
1898
+ __name(isNoLongerPublic, "isNoLongerPublic");
1899
+ var UI_VUE_SPECIFIER = /@saasicat\/ui-vue\/([A-Za-z0-9/_.-]+)/g;
1900
+ function rewriteImports(text, map) {
1901
+ const unmapped = /* @__PURE__ */ new Map();
1902
+ let rewritten = 0;
1903
+ const next = text.replace(UI_VUE_SPECIFIER, (whole, subpath) => {
1904
+ const to = rewriteSubpath(map, subpath);
1905
+ if (to === null) {
1906
+ if (isNoLongerPublic(map, subpath)) {
1907
+ unmapped.set(subpath, (unmapped.get(subpath) ?? 0) + 1);
1908
+ }
1909
+ return whole;
1910
+ }
1911
+ rewritten += 1;
1912
+ return to.startsWith("@") ? to : `@saasicat/ui-vue/${to}`;
1913
+ });
1914
+ return {
1915
+ text: next,
1916
+ rewritten,
1917
+ unmapped
1918
+ };
1919
+ }
1920
+ __name(rewriteImports, "rewriteImports");
1921
+
1922
+ // src/codemods/v1-rename.ts
1923
+ var escape = /* @__PURE__ */ __name((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"), "escape");
1924
+ var FROM_SPECIFIER = /from\s*(['"])([^'"]+)\1/g;
1925
+ var isSpace = /* @__PURE__ */ __name((ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r", "isSpace");
1926
+ function namedImports(text) {
1927
+ const found = [];
1928
+ for (const match of text.matchAll(FROM_SPECIFIER)) {
1929
+ let i = (match.index ?? 0) - 1;
1930
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1931
+ if (text[i] !== "}") continue;
1932
+ const close = i;
1933
+ const open = text.lastIndexOf("{", close);
1934
+ if (open < 0) continue;
1935
+ i = open - 1;
1936
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1937
+ let head = text.slice(Math.max(0, i - 3), i + 1);
1938
+ if (head === "type") {
1939
+ i -= 4;
1940
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1941
+ head = text.slice(Math.max(0, i - 5), i + 1);
1942
+ } else {
1943
+ head = text.slice(Math.max(0, i - 5), i + 1);
1944
+ }
1945
+ if (head !== "import") continue;
1946
+ const names = text.slice(open + 1, close).split(",").map((raw) => {
1947
+ const words = raw.trim().split(/\s+/);
1948
+ if (words[0] === "type") words.shift();
1949
+ return words[0] ?? "";
1950
+ }).filter((name) => name.length > 0);
1951
+ found.push({
1952
+ names,
1953
+ specifier: match[2]
1954
+ });
1955
+ }
1956
+ return found;
1957
+ }
1958
+ __name(namedImports, "namedImports");
1959
+ function rewriteNames(text, table) {
1960
+ let next = text;
1961
+ let rewritten = 0;
1962
+ const ambiguous = /* @__PURE__ */ new Set();
1963
+ const perEntry = /* @__PURE__ */ new Map();
1964
+ for (const { names, specifier } of namedImports(text)) {
1965
+ const mapping = table.entryTokens[specifier];
1966
+ for (const name of names) {
1967
+ const knownSomewhere = Object.values(table.entryTokens).some((m) => name in m);
1968
+ if (!knownSomewhere) continue;
1969
+ const already = perEntry.get(name);
1970
+ if (mapping && name in mapping && (already === void 0 || already === mapping[name])) {
1971
+ perEntry.set(name, mapping[name]);
1972
+ } else {
1973
+ if (already !== void 0) perEntry.delete(name);
1974
+ ambiguous.add(`${name} from '${specifier}'`);
1975
+ }
1976
+ }
1977
+ }
1978
+ for (const [from, to] of perEntry) {
1979
+ next = next.replace(new RegExp(`\\b${escape(from)}\\b`, "g"), () => {
1980
+ rewritten += 1;
1981
+ return to;
1982
+ });
1983
+ }
1984
+ for (const [from, to] of Object.entries(table.identifierStems)) {
1985
+ next = next.replace(new RegExp(escape(from), "g"), () => {
1986
+ rewritten += 1;
1987
+ return to;
1988
+ });
1989
+ }
1990
+ for (const [from, to] of Object.entries(table.registryKeys)) {
1991
+ const symbolFor = new RegExp(`(Symbol\\.for\\(\\s*['"\`])${escape(from)}`, "g");
1992
+ next = next.replace(symbolFor, (_, head) => {
1993
+ rewritten += 1;
1994
+ return `${head}${to}`;
1995
+ });
1996
+ if (from.startsWith("@")) continue;
1997
+ const literal = new RegExp(`(['"\`])${escape(from)}`, "g");
1998
+ next = next.replace(literal, (_, quote) => {
1999
+ rewritten += 1;
2000
+ return `${quote}${to}`;
2001
+ });
2002
+ }
2003
+ for (const [from, to] of Object.entries(table.packages ?? {})) {
2004
+ if (from === "_") continue;
2005
+ next = next.replace(new RegExp(`${escape(from)}(?=['"\`/])`, "g"), () => {
2006
+ rewritten += 1;
2007
+ return to;
2008
+ });
2009
+ }
2010
+ for (const [from, to] of Object.entries(table.subpaths)) {
2011
+ next = next.replace(new RegExp(escape(from), "g"), () => {
2012
+ rewritten += 1;
2013
+ return to;
2014
+ });
2015
+ }
2016
+ return {
2017
+ text: next,
2018
+ rewritten,
2019
+ ambiguous: [
2020
+ ...ambiguous
2021
+ ].sort()
2022
+ };
2023
+ }
2024
+ __name(rewriteNames, "rewriteNames");
2025
+ var DEPENDENCY_FIELDS = [
2026
+ "dependencies",
2027
+ "devDependencies",
2028
+ "peerDependencies",
2029
+ "optionalDependencies"
2030
+ ];
2031
+ var UNTRANSLATABLE_RANGE = /^(workspace:|file:|link:|npm:|git\+|https?:)/;
2032
+ function rewriteManifest(text, table, options) {
2033
+ const renames = Object.entries(table.packages ?? {}).filter(([from]) => from !== "_");
2034
+ const ambiguous = [];
2035
+ let manifest;
2036
+ try {
2037
+ manifest = JSON.parse(text);
2038
+ } catch {
2039
+ return {
2040
+ text,
2041
+ rewritten: 0,
2042
+ ambiguous
2043
+ };
2044
+ }
2045
+ let rewritten = 0;
2046
+ for (const field of DEPENDENCY_FIELDS) {
2047
+ const deps = manifest[field];
2048
+ if (!deps || typeof deps !== "object") continue;
2049
+ const entries = Object.entries(deps);
2050
+ const renamed = entries.map(([name, range]) => {
2051
+ const to = renames.find(([from]) => from === name)?.[1];
2052
+ if (!to) return [
2053
+ name,
2054
+ range
2055
+ ];
2056
+ if (UNTRANSLATABLE_RANGE.test(range)) {
2057
+ ambiguous.push(`${name} in ${field} (${range})`);
2058
+ return [
2059
+ name,
2060
+ range
2061
+ ];
2062
+ }
2063
+ rewritten += 1;
2064
+ return [
2065
+ to,
2066
+ options.targetRange
2067
+ ];
2068
+ });
2069
+ manifest[field] = Object.fromEntries(renamed);
2070
+ }
2071
+ const meta = manifest.peerDependenciesMeta;
2072
+ if (meta && typeof meta === "object") {
2073
+ manifest.peerDependenciesMeta = Object.fromEntries(Object.entries(meta).map(([name, flags]) => {
2074
+ const to = renames.find(([from]) => from === name)?.[1];
2075
+ if (!to) return [
2076
+ name,
2077
+ flags
2078
+ ];
2079
+ rewritten += 1;
2080
+ return [
2081
+ to,
2082
+ flags
2083
+ ];
2084
+ }));
2085
+ }
2086
+ if (rewritten === 0) return {
2087
+ text,
2088
+ rewritten: 0,
2089
+ ambiguous
2090
+ };
2091
+ const indent = /^[ \t]+/m.exec(text)?.[0] ?? " ";
2092
+ const trailing = text.endsWith("\n") ? "\n" : "";
2093
+ return {
2094
+ text: JSON.stringify(manifest, null, indent) + trailing,
2095
+ rewritten,
2096
+ ambiguous
2097
+ };
2098
+ }
2099
+ __name(rewriteManifest, "rewriteManifest");
2100
+
1830
2101
  // src/init/patch-app-module.ts
1831
2102
  var MARKER = "SaaSiCatModule.forRoot";
1832
2103
  function patchAppModule(source, options) {
@@ -1951,7 +2222,7 @@ var LIMIT_FILTER_IMPORTS = [
1951
2222
  "import { LimitExceededFilter } from '@saasicat/nest/billing';"
1952
2223
  ].join("\n");
1953
2224
 
1954
- // src/module.ts
2225
+ // src/cli-context.module.ts
1955
2226
  var import_common8 = require("@nestjs/common");
1956
2227
  var import_nest5 = require("@saasicat/nest");
1957
2228
  function _ts_decorate8(decorators, target, key, desc) {
@@ -3037,6 +3308,7 @@ UserCommands = _ts_decorate14([
3037
3308
  MfaSetupFlow,
3038
3309
  PLATFORM_DOCTOR_CHECK_PROVIDERS,
3039
3310
  PlanCatalogDoctorCheck,
3311
+ UI_VUE_SPECIFIER,
3040
3312
  USER_MANAGEMENT_PORT_TOKEN,
3041
3313
  USER_PORT_TOKEN,
3042
3314
  UserCommands,
@@ -3051,6 +3323,7 @@ UserCommands = _ts_decorate14([
3051
3323
  blankStringLiterals,
3052
3324
  blockBodyLines,
3053
3325
  breaksContract,
3326
+ buildImportMap,
3054
3327
  checkSchema,
3055
3328
  constraintsFor,
3056
3329
  enableFkPointers,
@@ -3062,10 +3335,12 @@ UserCommands = _ts_decorate14([
3062
3335
  foreignKeyOf,
3063
3336
  hasBackRelation,
3064
3337
  hasConstraints,
3338
+ isNoLongerPublic,
3065
3339
  isOneToOne,
3066
3340
  kebabCase,
3067
3341
  migrationCreatedBy,
3068
3342
  minimumQuotasPerPlan,
3343
+ namedImports,
3069
3344
  parseBlockAttributes,
3070
3345
  parseEnumValues,
3071
3346
  parseFields,
@@ -3079,6 +3354,10 @@ UserCommands = _ts_decorate14([
3079
3354
  quotaKeyPattern,
3080
3355
  relationNameOf,
3081
3356
  reportConstraints,
3357
+ rewriteImports,
3358
+ rewriteManifest,
3359
+ rewriteNames,
3360
+ rewriteSubpath,
3082
3361
  stripLineComment,
3083
3362
  structuralOnly,
3084
3363
  tablesAddressedBy
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MfaService, AdminAuditService, AdminManifestService, DiscoverySnapshot, ProviderSpec, DiscoveryScanner } from '@saasicat/nest';
2
- import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/types';
2
+ import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/core';
3
3
  import { Type, DynamicModule } from '@nestjs/common';
4
4
  import { CommandRunner } from 'nest-commander';
5
5
 
@@ -820,6 +820,117 @@ declare function assertValidProjectKey(projectKey: string): void;
820
820
  /** The same, for a quota key. `additionalProperties: false` makes it a hard rule. */
821
821
  declare function assertValidQuotaKey(quotaKey: string): void;
822
822
 
823
+ /** One entry of the move table: where a file was, and where it went. */
824
+ interface MoveTable {
825
+ readonly moves: Readonly<Record<string, string>>;
826
+ /**
827
+ * Prefixes that left the package entirely. The value is a full specifier
828
+ * — `@saasicat/ui-vue-tenant/` — and is emitted verbatim.
829
+ */
830
+ readonly packages?: Readonly<Record<string, string>>;
831
+ /**
832
+ * Directories whose files left the surface as a whole — the page-private
833
+ * parts under `pages-standard/<page>/` that became `internal/<page>/`.
834
+ */
835
+ readonly moveDirectories?: Readonly<Record<string, string>>;
836
+ }
837
+ /**
838
+ * Old subpath → new subpath, derived from the move table.
839
+ *
840
+ * Both spellings of a page that moved are mapped, because both were reachable:
841
+ * `pages/AdminLayout.vue` and `pages-standard/AdminLayout.vue` named one file.
842
+ */
843
+ declare function buildImportMap(table: MoveTable): Map<string, string>;
844
+ /**
845
+ * What a subpath becomes, or null when it is already right.
846
+ *
847
+ * A `pages-standard/` path with no entry in the table is a page that did not
848
+ * move: it keeps its name under the surviving alias.
849
+ */
850
+ declare function rewriteSubpath(map: ReadonlyMap<string, string>, subpath: string): string | null;
851
+ /**
852
+ * Whether a subpath was public before and is not any more.
853
+ *
854
+ * Reported rather than rewritten: it moved into `features/` or `internal/`,
855
+ * which the 1.0 surface does not publish, so there is no destination to point
856
+ * at. Leaving it silently would hand the consumer a build error with no
857
+ * explanation of what happened.
858
+ */
859
+ declare function isNoLongerPublic(map: ReadonlyMap<string, string>, subpath: string): boolean;
860
+ /** Every `@saasicat/ui-vue/<subpath>` occurrence in a source text. */
861
+ declare const UI_VUE_SPECIFIER: RegExp;
862
+ interface RewriteResult {
863
+ readonly text: string;
864
+ readonly rewritten: number;
865
+ /** Subpaths that lost their export, with how often each appeared. */
866
+ readonly unmapped: ReadonlyMap<string, number>;
867
+ }
868
+ /** Applies the map to one file's text. */
869
+ declare function rewriteImports(text: string, map: ReadonlyMap<string, string>): RewriteResult;
870
+
871
+ /** One entry of the rename table. */
872
+ interface RenameTable {
873
+ /** An identifier stem, matched anywhere in an identifier, and its replacement. */
874
+ readonly identifierStems: Readonly<Record<string, string>>;
875
+ /** A registry-key prefix (or a whole key) inside a string literal, and its replacement. */
876
+ readonly registryKeys: Readonly<Record<string, string>>;
877
+ /** Per import specifier: a name that means something different per entry. */
878
+ readonly entryTokens: Readonly<Record<string, Readonly<Record<string, string>>>>;
879
+ /** A module specifier prefix and its replacement. */
880
+ readonly subpaths: Readonly<Record<string, string>>;
881
+ /**
882
+ * A package that was renamed. Rewritten in specifiers by `rewriteNames`
883
+ * and in `package.json` dependency fields by `rewriteManifest` — both,
884
+ * because an import a manifest does not declare fails to resolve under
885
+ * pnpm's isolated `node_modules`.
886
+ */
887
+ readonly packages?: Readonly<Record<string, string>>;
888
+ }
889
+ interface RenameResult {
890
+ readonly text: string;
891
+ readonly rewritten: number;
892
+ /**
893
+ * Names the table knows only per entry, imported from somewhere the table
894
+ * does not cover. Reported rather than guessed: which registry the
895
+ * consumer meant is not in the text.
896
+ */
897
+ readonly ambiguous: readonly string[];
898
+ }
899
+ /**
900
+ * The specifier and the bound names of every `import { … } from '…'`.
901
+ *
902
+ * Read backwards from each `from`, one character at a time, instead of with
903
+ * one regular expression over the statement: `\{([^}]*)\}\s+from` and its
904
+ * siblings backtrack quadratically on a file full of `import {{`, and the
905
+ * file is a consumer's — whatever they wrote, this must finish.
906
+ */
907
+ declare function namedImports(text: string): Array<{
908
+ names: string[];
909
+ specifier: string;
910
+ }>;
911
+ /** Applies the table to one file's text. Idempotent: a second run changes nothing. */
912
+ declare function rewriteNames(text: string, table: RenameTable): RenameResult;
913
+ /**
914
+ * Applies the package renames to a `package.json` text.
915
+ *
916
+ * Parsed and re-serialised rather than string-replaced, so a rename lands in a
917
+ * dependency field and nowhere else — not in `name`, not in a description.
918
+ * The file's indentation is kept; a consumer's formatter must not see a diff
919
+ * it did not cause. Returns the text unchanged when nothing applied.
920
+ */
921
+ interface ManifestRewriteOptions {
922
+ /**
923
+ * The range the renamed dependency gets — `^<the version this CLI was
924
+ * released as>`. The old range cannot be carried over: a 0.x consumer
925
+ * declares `"@saasicat/types": "^0.27.0"`, and `@saasicat/core` has no
926
+ * 0.27 — the rename starts on the 1.0 line. The caller passes the CLI's
927
+ * own version because that IS the line the consumer is migrating to;
928
+ * the codemod ships with it.
929
+ */
930
+ readonly targetRange: string;
931
+ }
932
+ declare function rewriteManifest(text: string, table: RenameTable, options: ManifestRewriteOptions): RenameResult;
933
+
823
934
  interface PatchAppModuleOptions {
824
935
  /** Import specifier for the persistence bundle, or null when not generated. */
825
936
  persistenceImport: string | null;
@@ -1063,4 +1174,4 @@ declare class UserCommands extends CommandRunner {
1063
1174
  parsePassword(val: string): string;
1064
1175
  }
1065
1176
 
1066
- 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, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type QuotaProviderFile, type QuotaSpec, type SchemaCheckReport, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidProjectKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, checkSchema, constraintsFor, enableFkPointers, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, findFkPointers, foreignKeyOf, hasBackRelation, hasConstraints, isOneToOne, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, projectKeyPattern, quotaKeyPattern, relationNameOf, reportConstraints, stripLineComment, structuralOnly, tablesAddressedBy };
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 };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MfaService, AdminAuditService, AdminManifestService, DiscoverySnapshot, ProviderSpec, DiscoveryScanner } from '@saasicat/nest';
2
- import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/types';
2
+ import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/core';
3
3
  import { Type, DynamicModule } from '@nestjs/common';
4
4
  import { CommandRunner } from 'nest-commander';
5
5
 
@@ -820,6 +820,117 @@ declare function assertValidProjectKey(projectKey: string): void;
820
820
  /** The same, for a quota key. `additionalProperties: false` makes it a hard rule. */
821
821
  declare function assertValidQuotaKey(quotaKey: string): void;
822
822
 
823
+ /** One entry of the move table: where a file was, and where it went. */
824
+ interface MoveTable {
825
+ readonly moves: Readonly<Record<string, string>>;
826
+ /**
827
+ * Prefixes that left the package entirely. The value is a full specifier
828
+ * — `@saasicat/ui-vue-tenant/` — and is emitted verbatim.
829
+ */
830
+ readonly packages?: Readonly<Record<string, string>>;
831
+ /**
832
+ * Directories whose files left the surface as a whole — the page-private
833
+ * parts under `pages-standard/<page>/` that became `internal/<page>/`.
834
+ */
835
+ readonly moveDirectories?: Readonly<Record<string, string>>;
836
+ }
837
+ /**
838
+ * Old subpath → new subpath, derived from the move table.
839
+ *
840
+ * Both spellings of a page that moved are mapped, because both were reachable:
841
+ * `pages/AdminLayout.vue` and `pages-standard/AdminLayout.vue` named one file.
842
+ */
843
+ declare function buildImportMap(table: MoveTable): Map<string, string>;
844
+ /**
845
+ * What a subpath becomes, or null when it is already right.
846
+ *
847
+ * A `pages-standard/` path with no entry in the table is a page that did not
848
+ * move: it keeps its name under the surviving alias.
849
+ */
850
+ declare function rewriteSubpath(map: ReadonlyMap<string, string>, subpath: string): string | null;
851
+ /**
852
+ * Whether a subpath was public before and is not any more.
853
+ *
854
+ * Reported rather than rewritten: it moved into `features/` or `internal/`,
855
+ * which the 1.0 surface does not publish, so there is no destination to point
856
+ * at. Leaving it silently would hand the consumer a build error with no
857
+ * explanation of what happened.
858
+ */
859
+ declare function isNoLongerPublic(map: ReadonlyMap<string, string>, subpath: string): boolean;
860
+ /** Every `@saasicat/ui-vue/<subpath>` occurrence in a source text. */
861
+ declare const UI_VUE_SPECIFIER: RegExp;
862
+ interface RewriteResult {
863
+ readonly text: string;
864
+ readonly rewritten: number;
865
+ /** Subpaths that lost their export, with how often each appeared. */
866
+ readonly unmapped: ReadonlyMap<string, number>;
867
+ }
868
+ /** Applies the map to one file's text. */
869
+ declare function rewriteImports(text: string, map: ReadonlyMap<string, string>): RewriteResult;
870
+
871
+ /** One entry of the rename table. */
872
+ interface RenameTable {
873
+ /** An identifier stem, matched anywhere in an identifier, and its replacement. */
874
+ readonly identifierStems: Readonly<Record<string, string>>;
875
+ /** A registry-key prefix (or a whole key) inside a string literal, and its replacement. */
876
+ readonly registryKeys: Readonly<Record<string, string>>;
877
+ /** Per import specifier: a name that means something different per entry. */
878
+ readonly entryTokens: Readonly<Record<string, Readonly<Record<string, string>>>>;
879
+ /** A module specifier prefix and its replacement. */
880
+ readonly subpaths: Readonly<Record<string, string>>;
881
+ /**
882
+ * A package that was renamed. Rewritten in specifiers by `rewriteNames`
883
+ * and in `package.json` dependency fields by `rewriteManifest` — both,
884
+ * because an import a manifest does not declare fails to resolve under
885
+ * pnpm's isolated `node_modules`.
886
+ */
887
+ readonly packages?: Readonly<Record<string, string>>;
888
+ }
889
+ interface RenameResult {
890
+ readonly text: string;
891
+ readonly rewritten: number;
892
+ /**
893
+ * Names the table knows only per entry, imported from somewhere the table
894
+ * does not cover. Reported rather than guessed: which registry the
895
+ * consumer meant is not in the text.
896
+ */
897
+ readonly ambiguous: readonly string[];
898
+ }
899
+ /**
900
+ * The specifier and the bound names of every `import { … } from '…'`.
901
+ *
902
+ * Read backwards from each `from`, one character at a time, instead of with
903
+ * one regular expression over the statement: `\{([^}]*)\}\s+from` and its
904
+ * siblings backtrack quadratically on a file full of `import {{`, and the
905
+ * file is a consumer's — whatever they wrote, this must finish.
906
+ */
907
+ declare function namedImports(text: string): Array<{
908
+ names: string[];
909
+ specifier: string;
910
+ }>;
911
+ /** Applies the table to one file's text. Idempotent: a second run changes nothing. */
912
+ declare function rewriteNames(text: string, table: RenameTable): RenameResult;
913
+ /**
914
+ * Applies the package renames to a `package.json` text.
915
+ *
916
+ * Parsed and re-serialised rather than string-replaced, so a rename lands in a
917
+ * dependency field and nowhere else — not in `name`, not in a description.
918
+ * The file's indentation is kept; a consumer's formatter must not see a diff
919
+ * it did not cause. Returns the text unchanged when nothing applied.
920
+ */
921
+ interface ManifestRewriteOptions {
922
+ /**
923
+ * The range the renamed dependency gets — `^<the version this CLI was
924
+ * released as>`. The old range cannot be carried over: a 0.x consumer
925
+ * declares `"@saasicat/types": "^0.27.0"`, and `@saasicat/core` has no
926
+ * 0.27 — the rename starts on the 1.0 line. The caller passes the CLI's
927
+ * own version because that IS the line the consumer is migrating to;
928
+ * the codemod ships with it.
929
+ */
930
+ readonly targetRange: string;
931
+ }
932
+ declare function rewriteManifest(text: string, table: RenameTable, options: ManifestRewriteOptions): RenameResult;
933
+
823
934
  interface PatchAppModuleOptions {
824
935
  /** Import specifier for the persistence bundle, or null when not generated. */
825
936
  persistenceImport: string | null;
@@ -1063,4 +1174,4 @@ declare class UserCommands extends CommandRunner {
1063
1174
  parsePassword(val: string): string;
1064
1175
  }
1065
1176
 
1066
- 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, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type QuotaProviderFile, type QuotaSpec, type SchemaCheckReport, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WhoAmIFlow, type WhoAmIResult, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidProjectKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, checkSchema, constraintsFor, enableFkPointers, extractBlockNames, extractBlocks, extractModelBlocks, extractModelNames, findFkPointers, foreignKeyOf, hasBackRelation, hasConstraints, isOneToOne, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, projectKeyPattern, quotaKeyPattern, relationNameOf, reportConstraints, stripLineComment, structuralOnly, tablesAddressedBy };
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 };