@saasicat/cli 0.27.0 → 1.0.0-rc.0

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/README.md CHANGED
@@ -10,7 +10,7 @@ Cross-cutting helpers for consumer CLIs. Provides:
10
10
  - `ManifestCliFlow` + `DEFAULT_MANIFEST_CHECKS` (12 platform checks) —
11
11
  `<app> manifest dump|validate|hash|diff|check`
12
12
 
13
- Spec: [`saas-platform-spec/cli-conventions.md`](https://github.com/uelker70/saasicat/blob/main/packages/saas-platform-spec/cli-conventions.md).
13
+ Spec: [`cli-conventions.md`][conventions] in `@saasicat/spec`.
14
14
 
15
15
  ## Plugin Architecture
16
16
 
@@ -126,7 +126,7 @@ domain commands on top.
126
126
 
127
127
  ## Exit codes
128
128
 
129
- Per [`cli-conventions.md`](https://github.com/uelker70/saasicat/blob/main/packages/saas-platform-spec/cli-conventions.md) §6:
129
+ Per [`cli-conventions.md`][conventions] §6:
130
130
 
131
131
  | Code | Meaning |
132
132
  | ---- | --------------------------------------- |
@@ -139,3 +139,5 @@ Per [`cli-conventions.md`](https://github.com/uelker70/saasicat/blob/main/packag
139
139
  | 6 | conflict |
140
140
  | 7 | drift (e.g. manifest check: error) |
141
141
  | 99 | internal |
142
+
143
+ [conventions]: https://github.com/uelker70/saasicat/blob/main/packages/spec/cli-conventions.md
package/bin/saasicat.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // `saasicat` — bootstrap CLI for the SaaSiCat framework.
3
+ // naming-history: the codemod help below names the pre-1.0 spellings it rewrites.
3
4
  //
4
5
  // Sub-commands:
5
6
  // schema apply [--prisma-schema=PATH] [--fragments=01,02,03]
@@ -16,6 +17,9 @@
16
17
  // Prisma's DSL cannot express to the migration it just wrote.
17
18
  //
18
19
  // init --project-key=X --quota=key:Model [--quota=…]... [--app-name=X] [--api-base=X]
20
+ // codemod v1-imports [--dir=X] [--dry-run]
21
+ // codemod v1-rename [--dir=X] [--dry-run]
22
+ // codemod v1 [--dir=X] [--dry-run] — both, in that order
19
23
  // [--skip-hasher] [--dry-run] [--dir=.]
20
24
  // Writes the platform wiring — config, persistence, manifest
21
25
  // contribution, admin module, one provider per quota — and adds
@@ -43,7 +47,10 @@ import {
43
47
  findFkPointers,
44
48
  hasConstraints,
45
49
  assertValidProjectKey,
50
+ buildImportMap,
46
51
  migrationCreatedBy,
52
+ rewriteImports,
53
+ rewriteNames,
47
54
  reportConstraints,
48
55
  patchAppModule,
49
56
  patchOptionsFor,
@@ -523,6 +530,124 @@ async function writeConstraintsIntoMigration(args, migrationsBefore) {
523
530
  }
524
531
  }
525
532
 
533
+ /**
534
+ * Rewrites `@saasicat/ui-vue` imports to the 1.0 export map.
535
+ *
536
+ * The rules come from the same table the platform's own move ran on, shipped
537
+ * with this package — so what a consumer's imports become cannot disagree with
538
+ * where the files actually went.
539
+ */
540
+ async function cmdCodemodV1Imports(args) {
541
+ const root = resolve(args.dir ?? '.');
542
+ const dryRun = args['dry-run'] === true;
543
+
544
+ const table = JSON.parse(await readFile(codemodTable('v1-imports.map.json'), 'utf8'));
545
+ const map = buildImportMap(table);
546
+
547
+ const unmapped = new Map();
548
+ let rewritten = 0;
549
+ let touched = 0;
550
+ await walkSources(root, async (full, source) => {
551
+ const result = rewriteImports(source, map);
552
+ for (const [subpath, n] of result.unmapped) {
553
+ unmapped.set(subpath, (unmapped.get(subpath) ?? 0) + n);
554
+ }
555
+ if (result.rewritten === 0) return;
556
+ if (!dryRun) await writeFile(full, result.text);
557
+ rewritten += result.rewritten;
558
+ touched += 1;
559
+ });
560
+
561
+ console.log(
562
+ `${dryRun ? 'Would rewrite' : 'Rewrote'} ${rewritten} import(s) in ${touched} file(s).`,
563
+ );
564
+ if (unmapped.size === 0) return;
565
+
566
+ console.log('');
567
+ console.log('These have no new home — they need a decision, not a rewrite:');
568
+ for (const [subpath, n] of [...unmapped].sort()) {
569
+ console.log(` ${String(n).padStart(3)}× @saasicat/ui-vue/${subpath}`);
570
+ }
571
+ console.log('');
572
+ console.log(' They moved into `features/` or `internal/`, which the 1.0 surface does');
573
+ console.log(' not publish: they were domain or page-private components, and importing');
574
+ console.log(' them tied your app to our internal structure. Copy what you need into');
575
+ console.log(' your own repository.');
576
+ }
577
+
578
+ /**
579
+ * Rewrites the names 1.0 changed: identifier stems, registry keys, the one
580
+ * token that had two meanings, and the e2e helper's subpath.
581
+ *
582
+ * Same shape as `v1-imports`, same table discipline: the rules are read from
583
+ * `codemods/v1-rename.map.json`, shipped with this package, so what a
584
+ * consumer's code becomes is what the platform's own rename was checked
585
+ * against.
586
+ */
587
+ async function cmdCodemodV1Rename(args) {
588
+ const root = resolve(args.dir ?? '.');
589
+ const dryRun = args['dry-run'] === true;
590
+ const table = JSON.parse(await readFile(codemodTable('v1-rename.map.json'), 'utf8'));
591
+
592
+ const ambiguous = new Map();
593
+ let rewritten = 0;
594
+ let touched = 0;
595
+ await walkSources(root, async (full, source) => {
596
+ const result = rewriteNames(source, table);
597
+ for (const name of result.ambiguous) {
598
+ ambiguous.set(name, (ambiguous.get(name) ?? 0) + 1);
599
+ }
600
+ if (result.rewritten === 0) return;
601
+ if (!dryRun) await writeFile(full, result.text);
602
+ rewritten += result.rewritten;
603
+ touched += 1;
604
+ });
605
+
606
+ console.log(
607
+ `${dryRun ? 'Would rewrite' : 'Rewrote'} ${rewritten} name(s) in ${touched} file(s).`,
608
+ );
609
+ if (ambiguous.size === 0) return;
610
+
611
+ console.log('');
612
+ console.log('These need a decision, not a rewrite:');
613
+ for (const [name, n] of [...ambiguous].sort()) {
614
+ console.log(` ${String(n).padStart(3)}× ${name}`);
615
+ }
616
+ console.log('');
617
+ console.log(' FEATURE_UI_REGISTRY_TOKEN meant one registry in `@saasicat/nest/billing`');
618
+ console.log(' and another in `@saasicat/nest/catalog`. Import it from the entry you');
619
+ console.log(' mean — BILLING_FEATURE_UI_REGISTRY_TOKEN or CATALOG_FEATURE_UI_REGISTRY_TOKEN.');
620
+ }
621
+
622
+ /** Where a shipped codemod table lives, resolved through the package itself. */
623
+ function codemodTable(name) {
624
+ return join(dirname(require_.resolve('@saasicat/cli')), '..', 'codemods', name);
625
+ }
626
+
627
+ // Anything a build wrote is skipped, whatever it is called: `dist`, `dist-app`,
628
+ // `dist-dev` — a consumer's declaration output carries the old names too, and
629
+ // rewriting it would only make the next build disagree with it.
630
+ const CODEMOD_SKIP = new Set(['node_modules', '.git', '.output', 'coverage']);
631
+ const isBuildOutput = (name) => name === 'dist' || name.startsWith('dist-');
632
+ const CODEMOD_EXTENSIONS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue|md)$/;
633
+
634
+ /** Every source file under `root` a codemod may touch, with its text. */
635
+ async function walkSources(root, visit) {
636
+ const walk = async (dir) => {
637
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
638
+ if (CODEMOD_SKIP.has(entry.name) || isBuildOutput(entry.name)) continue;
639
+ const full = join(dir, entry.name);
640
+ if (entry.isDirectory()) {
641
+ await walk(full);
642
+ continue;
643
+ }
644
+ if (!CODEMOD_EXTENSIONS.test(entry.name)) continue;
645
+ await visit(full, await readFile(full, 'utf8'));
646
+ }
647
+ };
648
+ await walk(root);
649
+ }
650
+
526
651
  /** Reads a repeated flag (`--quota=a --quota=b`) off argv. */
527
652
  function repeatedFlag(argv, name) {
528
653
  return argv
@@ -662,6 +787,18 @@ async function patchAppModuleFile(root, plan, args) {
662
787
 
663
788
  async function main() {
664
789
  const [, , cmd, sub, ...rest] = process.argv;
790
+ if (cmd === 'codemod' && sub === 'v1-imports') {
791
+ return cmdCodemodV1Imports(parseArgs(rest));
792
+ }
793
+ if (cmd === 'codemod' && sub === 'v1-rename') {
794
+ return cmdCodemodV1Rename(parseArgs(rest));
795
+ }
796
+ if (cmd === 'codemod' && sub === 'v1') {
797
+ // Imports first: the rename table keys its per-entry tokens by the
798
+ // specifier they are imported from, which the import rewrite settles.
799
+ await cmdCodemodV1Imports(parseArgs(rest));
800
+ return cmdCodemodV1Rename(parseArgs(rest));
801
+ }
665
802
  if (cmd === 'init') {
666
803
  return cmdInit(parseArgs([sub, ...rest].filter(Boolean)), process.argv.slice(3));
667
804
  }
@@ -694,6 +831,14 @@ async function main() {
694
831
  console.log(' every plan must declare one, or the catalogue does not load.');
695
832
  console.log(' init --project-key=myapp --quota=notes:Note --quota=seats:Seat');
696
833
  console.log('');
834
+ console.log(' codemod v1 [--dir=.] [--dry-run]');
835
+ console.log(' the whole 1.0 migration: v1-imports, then v1-rename');
836
+ console.log(' codemod v1-imports [--dir=.] [--dry-run]');
837
+ console.log(' rewrite @saasicat/ui-vue imports to the 1.0 export map');
838
+ console.log(' codemod v1-rename [--dir=.] [--dry-run]');
839
+ console.log(' rewrite the names 1.0 changed: SaasPlatform*, Symbol.for keys,');
840
+ console.log(' FEATURE_UI_REGISTRY_TOKEN, @saasicat/ui-vue/testing-e2e/*');
841
+ console.log('');
697
842
  console.log('Optional --prisma-schema=PATH (default prisma/schema.prisma).');
698
843
  console.log('Optional --tenant-model=X --user-model=Y for apply/migrate — enables');
699
844
  console.log(' the foreign keys from the platform tables to your models.');
@@ -0,0 +1,135 @@
1
+ {
2
+ "_comment": [
3
+ "The 4.1 move, as data rather than as commands.",
4
+ "",
5
+ "Two axes decide every destination, and each directory answers exactly one",
6
+ "question (AP3 \u00a73.6): the LAYER a file belongs to (client / vue / quasar /",
7
+ "SFC) and how far it is REUSED \u2014 ui (surface-neutral, many domains) \u2192",
8
+ "features (one domain, many pages) \u2192 internal/<page> (one page) \u2192 pages",
9
+ "(one route).",
10
+ "",
11
+ "Written down because the mapping is the reviewable part. A 210-file move",
12
+ "expressed as `git mv` lines is a diff nobody can check; expressed as a",
13
+ "table, the question is whether each row is right.",
14
+ "",
15
+ "Two destinations differ from the sketch in AP3 \u00a73.6, and the reason is a",
16
+ "measurement rather than a preference. Consumers import `FeatureGate.vue` 19",
17
+ "times and `MfaPromptDialog.vue` 5 times across notesapp, vereinsfux and",
18
+ "autohauspro \u2014 `FeatureGate` is the single most imported component in the",
19
+ "package. AP3 places FeatureGate in `features/entitlement/`, and `features/`",
20
+ "is explicitly not exported; MfaPromptDialog is used by one page, which by",
21
+ "the reuse axis alone would put it in `internal/`. Either placement removes a",
22
+ "component three consumer repositories depend on.",
23
+ "",
24
+ "So both stay on the exported surface. A component a consumer embeds in their",
25
+ "own application is part of the public surface no matter how many of OUR",
26
+ "pages use it \u2014 the reuse axis measures our reuse, and that is not the same",
27
+ "question."
28
+ ],
29
+ "moves": {
30
+ "components/admin-page/AdminPage.vue": "ui/page/AdminPage.vue",
31
+ "components/admin-page/AdminHero.vue": "ui/page/AdminHero.vue",
32
+ "components/admin-page/AdminBody.vue": "ui/page/AdminBody.vue",
33
+ "components/admin-page/AdminSection.vue": "ui/page/AdminSection.vue",
34
+ "components/admin-page/AdminFilters.vue": "ui/page/AdminFilters.vue",
35
+ "components/admin-page/AdminAccordion.vue": "ui/page/AdminAccordion.vue",
36
+ "components/admin-page/AdminTable.vue": "ui/data/AdminTable.vue",
37
+ "components/admin-page/AdminPaginator.vue": "ui/data/AdminPaginator.vue",
38
+ "components/admin-page/AdminStatistics.vue": "ui/data/AdminStatistics.vue",
39
+ "components/admin-page/AdminKpi.vue": "ui/data/AdminKpi.vue",
40
+ "components/KvBlock.vue": "ui/data/KvBlock.vue",
41
+ "components/admin-page/AdminRefreshBtn.vue": "ui/feedback/AdminRefreshBtn.vue",
42
+ "components/wizard-stepper/WizardStepper.vue": "ui/page/WizardStepper.vue",
43
+ "components/plan/PlanCycleToggle.vue": "@saasicat/ui-vue-tenant/plan/PlanCycleToggle.vue",
44
+ "components/plan/PlanGrid.vue": "@saasicat/ui-vue-tenant/plan/PlanGrid.vue",
45
+ "components/plan/PriceSummary.vue": "@saasicat/ui-vue-tenant/plan/PriceSummary.vue",
46
+ "components/plan/PromoCodeInput.vue": "@saasicat/ui-vue-tenant/plan/PromoCodeInput.vue",
47
+ "components/plan/PublicBundleGrid.vue": "@saasicat/ui-vue-tenant/plan/PublicBundleGrid.vue",
48
+ "components/plan-create-dialog/PlanCreateDialog.vue": "features/plan/PlanCreateDialog.vue",
49
+ "components/plan-list/PlanList.vue": "features/plan/PlanList.vue",
50
+ "components/plan-matrix/PlanMatrix.vue": "features/plan/PlanMatrix.vue",
51
+ "components/plan-review/PlanReview.vue": "features/plan/PlanReview.vue",
52
+ "components/plan-detail/PlanDetail.vue": "features/plan/PlanDetail.vue",
53
+ "components/plan-detail/PlanTerminateDialog.vue": "features/plan/PlanTerminateDialog.vue",
54
+ "components/plan-detail/PlanAuditLog.vue": "features/plan/internal/PlanAuditLog.vue",
55
+ "components/plan-detail/PlanDetailKpis.vue": "features/plan/internal/PlanDetailKpis.vue",
56
+ "components/plan-detail/PlanTitleEdit.vue": "features/plan/internal/PlanTitleEdit.vue",
57
+ "components/plan-detail/PlanVersionDiffPanel.vue": "features/plan/internal/PlanVersionDiffPanel.vue",
58
+ "components/plan-detail/PlanVersionsPanel.vue": "features/plan/internal/PlanVersionsPanel.vue",
59
+ "components/plan-detail/types.ts": "features/plan/internal/plan-detail.types.ts",
60
+ "components/plan-version-editor/PlanVersionEditor.vue": "features/plan/PlanVersionEditor.vue",
61
+ "components/plan-version-editor/PlanCatalogPreview.vue": "features/plan/internal/PlanCatalogPreview.vue",
62
+ "components/plan-version-editor/PlanComponentPool.vue": "features/plan/internal/PlanComponentPool.vue",
63
+ "components/plan-version-editor/PlanVersionBasket.vue": "features/plan/internal/PlanVersionBasket.vue",
64
+ "components/plan-version-editor/PlanVersionDiffDialog.vue": "features/plan/internal/PlanVersionDiffDialog.vue",
65
+ "components/plan-version-editor/PlanVersionEditorHeader.vue": "features/plan/internal/PlanVersionEditorHeader.vue",
66
+ "components/plan-version-editor/types.ts": "features/plan/internal/plan-version-editor.types.ts",
67
+ "components/bundle-editor/BundleVersionInlineEditor.vue": "features/bundle/BundleVersionInlineEditor.vue",
68
+ "components/bundle-editor/BundleVersionStrip.vue": "features/bundle/BundleVersionStrip.vue",
69
+ "components/BundleVersionPublishDialog.vue": "features/bundle/BundleVersionPublishDialog.vue",
70
+ "components/bundle-editor/BundleCreatePanel.vue": "features/bundle/internal/BundleCreatePanel.vue",
71
+ "components/bundle-editor/BundleFeaturesEditor.vue": "features/bundle/internal/BundleFeaturesEditor.vue",
72
+ "components/bundle-editor/BundlePlanCompatPicker.vue": "features/bundle/internal/BundlePlanCompatPicker.vue",
73
+ "components/bundle-editor/BundleQuotasEditor.vue": "features/bundle/internal/BundleQuotasEditor.vue",
74
+ "components/bundle-editor/BundleStatusBanner.vue": "features/bundle/internal/BundleStatusBanner.vue",
75
+ "components/bundle-editor/bundle-version-status.ts": "features/bundle/internal/bundle-version-status.ts",
76
+ "components/bundle-editor/catalog-i18n.ts": "features/bundle/internal/catalog-i18n.ts",
77
+ "components/FeatureGate.vue": "ui/entitlement/FeatureGate.vue",
78
+ "components/dialogs/PilotCreateDialog.vue": "internal/dialogs/PilotCreateDialog.vue",
79
+ "components/dialogs/PilotEditDialog.vue": "internal/dialogs/PilotEditDialog.vue",
80
+ "components/dialogs/PromoCodeCreateDialog.vue": "internal/dialogs/PromoCodeCreateDialog.vue",
81
+ "components/dialogs/PromoCodeDialogFields.vue": "internal/dialogs/PromoCodeDialogFields.vue",
82
+ "components/dialogs/PromoCodeEditDialog.vue": "internal/dialogs/PromoCodeEditDialog.vue",
83
+ "components/dialogs/pilot-dialog.css": "internal/dialogs/pilot-dialog.css",
84
+ "components/dialogs/types.ts": "internal/dialogs/types.ts",
85
+ "pages-standard/AdminLayout.vue": "layouts/AdminLayout.vue",
86
+ "pages-standard/SuperAdminLoginPage.vue": "auth/SuperAdminLoginPage.vue",
87
+ "pages-standard/SuperAdminSetupWizard.vue": "auth/SuperAdminSetupWizard.vue",
88
+ "pages-standard/AdminManifestErrorPage.vue": "pages/AdminManifestErrorPage.vue",
89
+ "pages-standard/AuditPage.vue": "pages/AuditPage.vue",
90
+ "pages-standard/BundlesPage.vue": "pages/BundlesPage.vue",
91
+ "pages-standard/DashboardPage.vue": "pages/DashboardPage.vue",
92
+ "pages-standard/DiscoveryPage.vue": "pages/DiscoveryPage.vue",
93
+ "pages-standard/EmailHistoryPage.vue": "pages/EmailHistoryPage.vue",
94
+ "pages-standard/MarketingCatalogPage.vue": "pages/MarketingCatalogPage.vue",
95
+ "pages-standard/PilotsPage.vue": "pages/PilotsPage.vue",
96
+ "pages-standard/PlansPage.vue": "pages/PlansPage.vue",
97
+ "pages-standard/PlatformEmailPage.vue": "pages/PlatformEmailPage.vue",
98
+ "pages-standard/PromoCodeDetailPage.vue": "pages/PromoCodeDetailPage.vue",
99
+ "pages-standard/PromoCodesPage.vue": "pages/PromoCodesPage.vue",
100
+ "pages-standard/SubscriptionsPage.vue": "pages/SubscriptionsPage.vue",
101
+ "pages-standard/TenantDetailPage.vue": "pages/TenantDetailPage.vue",
102
+ "pages-standard/TenantsPage.vue": "pages/TenantsPage.vue",
103
+ "pages-standard/UsersPage.vue": "pages/UsersPage.vue",
104
+ "pages-standard/email-history.types.ts": "internal/email-history/email-history.types.ts",
105
+ "pages-standard/platform-email.types.ts": "internal/platform-email/platform-email.types.ts",
106
+ "components/MfaPromptDialog.vue": "ui/overlay/MfaPromptDialog.vue",
107
+ "components/LocaleSwitcher.vue": "ui/page/LocaleSwitcher.vue",
108
+ "components/ThemeSwitcher.vue": "ui/page/ThemeSwitcher.vue",
109
+ "components/MarketingPromotionsTab.vue": "features/marketing/MarketingPromotionsTab.vue",
110
+ "components/TenantActionConfirmDialog.vue": "features/tenant/TenantActionConfirmDialog.vue",
111
+ "components/VersionDiffPreview.vue": "features/bundle/VersionDiffPreview.vue",
112
+ "pages-standard/sa-theme.css": "ui/theme/sa-theme.css"
113
+ },
114
+ "moveDirectories": {
115
+ "pages-standard/bundles-page": "internal/bundles-page",
116
+ "pages-standard/discovery-page": "internal/discovery-page",
117
+ "pages-standard/marketing-catalog": "internal/marketing-catalog",
118
+ "pages-standard/plan-versions": "internal/plan-versions",
119
+ "pages-standard/plans-page": "internal/plans-page",
120
+ "pages-standard/tenant-detail": "internal/tenant-detail",
121
+ "pages-standard/tenants": "internal/tenants"
122
+ },
123
+ "packages": {
124
+ "_": "Subpaths that left @saasicat/ui-vue for a package of their own. The value is a full specifier, and the codemod emits it verbatim.",
125
+ "pages-tenant/": "@saasicat/ui-vue-tenant/"
126
+ },
127
+ "stays": {
128
+ "client/": "framework-free layer, unchanged",
129
+ "vue/": "composables, unchanged",
130
+ "quasar/": "the one non-SFC place that may import quasar, unchanged",
131
+ "ui/theme/": "already in its destination (AP2)",
132
+ "testing-e2e/ \u2192 testing/": "renamed in phase 5 (5.8); `codemod v1-rename` rewrites the specifier",
133
+ "pages-standard/sa-theme.css \u2192 ui/theme/": "moved so that pages-standard/ can go; renamed to theme.css in 4.2"
134
+ }
135
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "_namingHistory": "The 1.0 rename as data. Every old spelling below is named on purpose: this table is what `saasicat codemod v1-rename` runs on, and `tests/one-spelling.test.js` reads this key as the declaration.",
3
+ "_comment": [
4
+ "Phase 5 (AP1) renamed four things a consumer's code can carry:",
5
+ "identifier stems, registry keys, one token that had two meanings, and",
6
+ "one export subpath. Each section is one of those, and the codemod",
7
+ "applies them in that order.",
8
+ "",
9
+ "`identifierStems` is matched anywhere in an identifier — the stem alone",
10
+ "covers `SaasPlatformModule`, `createSaasPlatformTestModule` and",
11
+ "`CreateSaasPlatformTestModuleOptions` without listing them. The lowercase",
12
+ "`saasicat` (scope, files) is not a stem and is never touched.",
13
+ "",
14
+ "`registryKeys` rewrites the string a `Symbol.for` is called with. A",
15
+ "consumer that spelled a key themselves gets the same symbol the platform",
16
+ "now registers; without this their injection would resolve to nothing.",
17
+ "",
18
+ "`entryTokens` is keyed by the import specifier, because the old name",
19
+ "meant different registries in different entries. An import of it from",
20
+ "anywhere else is reported, not guessed."
21
+ ],
22
+ "identifierStems": {
23
+ "SaasPlatform": "SaaSiCat",
24
+ "Saasicat": "SaaSiCat",
25
+ "SaaSicat": "SaaSiCat"
26
+ },
27
+ "registryKeys": {
28
+ "saas-platform/": "saasicat/nest/",
29
+ "saas-platform-nest/": "saasicat/nest/",
30
+ "saas-platform-cli/": "saasicat/cli/",
31
+ "@saasicat/ui-vue/": "saasicat/ui-vue/",
32
+ "FakeTransactionRunner.tx": "saasicat/nest/FakeTransactionRunner.tx"
33
+ },
34
+ "entryTokens": {
35
+ "@saasicat/nest/billing": {
36
+ "FEATURE_UI_REGISTRY_TOKEN": "BILLING_FEATURE_UI_REGISTRY_TOKEN"
37
+ },
38
+ "@saasicat/nest/catalog": {
39
+ "FEATURE_UI_REGISTRY_TOKEN": "CATALOG_FEATURE_UI_REGISTRY_TOKEN"
40
+ }
41
+ },
42
+ "subpaths": {
43
+ "@saasicat/ui-vue/testing-e2e/": "@saasicat/ui-vue/testing/"
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ 87fdb737df2d686fe3d296add0d87c2a3765371380b486e2ff20fda896b3db65
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,23 @@ __export(index_exports, {
106
110
  quotaKeyPattern: () => quotaKeyPattern,
107
111
  relationNameOf: () => relationNameOf,
108
112
  reportConstraints: () => reportConstraints,
113
+ rewriteImports: () => rewriteImports,
114
+ rewriteNames: () => rewriteNames,
115
+ rewriteSubpath: () => rewriteSubpath,
109
116
  stripLineComment: () => stripLineComment,
110
117
  structuralOnly: () => structuralOnly,
111
118
  tablesAddressedBy: () => tablesAddressedBy
112
119
  });
113
120
  module.exports = __toCommonJS(index_exports);
114
121
 
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");
122
+ // src/cli.tokens.ts
123
+ var CLI_CONTEXT_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/Config");
124
+ var USER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/UserPort");
125
+ var USER_MANAGEMENT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/UserManagementPort");
126
+ var AUDIT_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/AuditQueryPort");
127
+ var DOCTOR_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/DoctorChecks");
128
+ var MANIFEST_ACCESS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/ManifestAccessPort");
129
+ var MANIFEST_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/ManifestChecks");
123
130
 
124
131
  // src/cli-context.service.ts
125
132
  var os = __toESM(require("os"), 1);
@@ -1827,6 +1834,187 @@ function patchOptionsFor(plan) {
1827
1834
  }
1828
1835
  __name(patchOptionsFor, "patchOptionsFor");
1829
1836
 
1837
+ // src/codemods/v1-imports.ts
1838
+ var PUBLIC_PREFIXES = [
1839
+ "ui/",
1840
+ "layouts/",
1841
+ "auth/",
1842
+ "pages/"
1843
+ ];
1844
+ function buildImportMap(table) {
1845
+ const map = /* @__PURE__ */ new Map();
1846
+ for (const [from, to] of Object.entries(table.moves)) {
1847
+ const onSurface = PUBLIC_PREFIXES.some((prefix) => to.startsWith(prefix));
1848
+ if (!onSurface && !to.startsWith("@")) continue;
1849
+ if (from.startsWith("components/")) {
1850
+ map.set(from, to);
1851
+ continue;
1852
+ }
1853
+ if (!from.startsWith("pages-standard/")) continue;
1854
+ const file = from.slice("pages-standard/".length);
1855
+ if (file.includes("/")) continue;
1856
+ map.set(from, to);
1857
+ map.set(`pages/${file}`, to);
1858
+ }
1859
+ for (const [from, to] of Object.entries(table.packages ?? {})) {
1860
+ if (from === "_") continue;
1861
+ map.set(from, to);
1862
+ }
1863
+ for (const [from, to] of Object.entries(table.moveDirectories ?? {})) {
1864
+ map.set(`${from}/`, `${to}/`);
1865
+ }
1866
+ return map;
1867
+ }
1868
+ __name(buildImportMap, "buildImportMap");
1869
+ function wentPrivate(map, subpath) {
1870
+ for (const [prefix, target] of map) {
1871
+ if (prefix.endsWith("/") && target.startsWith("internal/") && subpath.startsWith(prefix)) {
1872
+ return true;
1873
+ }
1874
+ }
1875
+ return false;
1876
+ }
1877
+ __name(wentPrivate, "wentPrivate");
1878
+ function rewriteSubpath(map, subpath) {
1879
+ const direct = map.get(subpath);
1880
+ if (direct !== void 0 && direct !== subpath) return direct;
1881
+ for (const [prefix, target] of map) {
1882
+ if (prefix.endsWith("/") && target.startsWith("@") && subpath.startsWith(prefix)) {
1883
+ return `${target}${subpath.slice(prefix.length)}`;
1884
+ }
1885
+ }
1886
+ if (subpath.startsWith("pages-standard/") && !wentPrivate(map, subpath)) {
1887
+ const file = subpath.slice("pages-standard/".length);
1888
+ if (!file.includes("/")) return `pages/${file}`;
1889
+ }
1890
+ return null;
1891
+ }
1892
+ __name(rewriteSubpath, "rewriteSubpath");
1893
+ function isNoLongerPublic(map, subpath) {
1894
+ if (subpath.startsWith("components/")) return !map.has(subpath);
1895
+ return subpath.startsWith("pages-standard/") && wentPrivate(map, subpath);
1896
+ }
1897
+ __name(isNoLongerPublic, "isNoLongerPublic");
1898
+ var UI_VUE_SPECIFIER = /@saasicat\/ui-vue\/([A-Za-z0-9/_.-]+)/g;
1899
+ function rewriteImports(text, map) {
1900
+ const unmapped = /* @__PURE__ */ new Map();
1901
+ let rewritten = 0;
1902
+ const next = text.replace(UI_VUE_SPECIFIER, (whole, subpath) => {
1903
+ const to = rewriteSubpath(map, subpath);
1904
+ if (to === null) {
1905
+ if (isNoLongerPublic(map, subpath)) {
1906
+ unmapped.set(subpath, (unmapped.get(subpath) ?? 0) + 1);
1907
+ }
1908
+ return whole;
1909
+ }
1910
+ rewritten += 1;
1911
+ return to.startsWith("@") ? to : `@saasicat/ui-vue/${to}`;
1912
+ });
1913
+ return {
1914
+ text: next,
1915
+ rewritten,
1916
+ unmapped
1917
+ };
1918
+ }
1919
+ __name(rewriteImports, "rewriteImports");
1920
+
1921
+ // src/codemods/v1-rename.ts
1922
+ var escape = /* @__PURE__ */ __name((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"), "escape");
1923
+ var FROM_SPECIFIER = /from\s*(['"])([^'"]+)\1/g;
1924
+ var isSpace = /* @__PURE__ */ __name((ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r", "isSpace");
1925
+ function namedImports(text) {
1926
+ const found = [];
1927
+ for (const match of text.matchAll(FROM_SPECIFIER)) {
1928
+ let i = (match.index ?? 0) - 1;
1929
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1930
+ if (text[i] !== "}") continue;
1931
+ const close = i;
1932
+ const open = text.lastIndexOf("{", close);
1933
+ if (open < 0) continue;
1934
+ i = open - 1;
1935
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1936
+ let head = text.slice(Math.max(0, i - 3), i + 1);
1937
+ if (head === "type") {
1938
+ i -= 4;
1939
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1940
+ head = text.slice(Math.max(0, i - 5), i + 1);
1941
+ } else {
1942
+ head = text.slice(Math.max(0, i - 5), i + 1);
1943
+ }
1944
+ if (head !== "import") continue;
1945
+ const names = text.slice(open + 1, close).split(",").map((raw) => {
1946
+ const words = raw.trim().split(/\s+/);
1947
+ if (words[0] === "type") words.shift();
1948
+ return words[0] ?? "";
1949
+ }).filter((name) => name.length > 0);
1950
+ found.push({
1951
+ names,
1952
+ specifier: match[2]
1953
+ });
1954
+ }
1955
+ return found;
1956
+ }
1957
+ __name(namedImports, "namedImports");
1958
+ function rewriteNames(text, table) {
1959
+ let next = text;
1960
+ let rewritten = 0;
1961
+ const ambiguous = /* @__PURE__ */ new Set();
1962
+ const perEntry = /* @__PURE__ */ new Map();
1963
+ for (const { names, specifier } of namedImports(text)) {
1964
+ const mapping = table.entryTokens[specifier];
1965
+ for (const name of names) {
1966
+ const knownSomewhere = Object.values(table.entryTokens).some((m) => name in m);
1967
+ if (!knownSomewhere) continue;
1968
+ const already = perEntry.get(name);
1969
+ if (mapping && name in mapping && (already === void 0 || already === mapping[name])) {
1970
+ perEntry.set(name, mapping[name]);
1971
+ } else {
1972
+ if (already !== void 0) perEntry.delete(name);
1973
+ ambiguous.add(`${name} from '${specifier}'`);
1974
+ }
1975
+ }
1976
+ }
1977
+ for (const [from, to] of perEntry) {
1978
+ next = next.replace(new RegExp(`\\b${escape(from)}\\b`, "g"), () => {
1979
+ rewritten += 1;
1980
+ return to;
1981
+ });
1982
+ }
1983
+ for (const [from, to] of Object.entries(table.identifierStems)) {
1984
+ next = next.replace(new RegExp(escape(from), "g"), () => {
1985
+ rewritten += 1;
1986
+ return to;
1987
+ });
1988
+ }
1989
+ for (const [from, to] of Object.entries(table.registryKeys)) {
1990
+ const symbolFor = new RegExp(`(Symbol\\.for\\(\\s*['"\`])${escape(from)}`, "g");
1991
+ next = next.replace(symbolFor, (_, head) => {
1992
+ rewritten += 1;
1993
+ return `${head}${to}`;
1994
+ });
1995
+ if (from.startsWith("@")) continue;
1996
+ const literal = new RegExp(`(['"\`])${escape(from)}`, "g");
1997
+ next = next.replace(literal, (_, quote) => {
1998
+ rewritten += 1;
1999
+ return `${quote}${to}`;
2000
+ });
2001
+ }
2002
+ for (const [from, to] of Object.entries(table.subpaths)) {
2003
+ next = next.replace(new RegExp(escape(from), "g"), () => {
2004
+ rewritten += 1;
2005
+ return to;
2006
+ });
2007
+ }
2008
+ return {
2009
+ text: next,
2010
+ rewritten,
2011
+ ambiguous: [
2012
+ ...ambiguous
2013
+ ].sort()
2014
+ };
2015
+ }
2016
+ __name(rewriteNames, "rewriteNames");
2017
+
1830
2018
  // src/init/patch-app-module.ts
1831
2019
  var MARKER = "SaaSiCatModule.forRoot";
1832
2020
  function patchAppModule(source, options) {
@@ -1951,7 +2139,7 @@ var LIMIT_FILTER_IMPORTS = [
1951
2139
  "import { LimitExceededFilter } from '@saasicat/nest/billing';"
1952
2140
  ].join("\n");
1953
2141
 
1954
- // src/module.ts
2142
+ // src/cli-context.module.ts
1955
2143
  var import_common8 = require("@nestjs/common");
1956
2144
  var import_nest5 = require("@saasicat/nest");
1957
2145
  function _ts_decorate8(decorators, target, key, desc) {
@@ -3037,6 +3225,7 @@ UserCommands = _ts_decorate14([
3037
3225
  MfaSetupFlow,
3038
3226
  PLATFORM_DOCTOR_CHECK_PROVIDERS,
3039
3227
  PlanCatalogDoctorCheck,
3228
+ UI_VUE_SPECIFIER,
3040
3229
  USER_MANAGEMENT_PORT_TOKEN,
3041
3230
  USER_PORT_TOKEN,
3042
3231
  UserCommands,
@@ -3051,6 +3240,7 @@ UserCommands = _ts_decorate14([
3051
3240
  blankStringLiterals,
3052
3241
  blockBodyLines,
3053
3242
  breaksContract,
3243
+ buildImportMap,
3054
3244
  checkSchema,
3055
3245
  constraintsFor,
3056
3246
  enableFkPointers,
@@ -3062,10 +3252,12 @@ UserCommands = _ts_decorate14([
3062
3252
  foreignKeyOf,
3063
3253
  hasBackRelation,
3064
3254
  hasConstraints,
3255
+ isNoLongerPublic,
3065
3256
  isOneToOne,
3066
3257
  kebabCase,
3067
3258
  migrationCreatedBy,
3068
3259
  minimumQuotasPerPlan,
3260
+ namedImports,
3069
3261
  parseBlockAttributes,
3070
3262
  parseEnumValues,
3071
3263
  parseFields,
@@ -3079,6 +3271,9 @@ UserCommands = _ts_decorate14([
3079
3271
  quotaKeyPattern,
3080
3272
  relationNameOf,
3081
3273
  reportConstraints,
3274
+ rewriteImports,
3275
+ rewriteNames,
3276
+ rewriteSubpath,
3082
3277
  stripLineComment,
3083
3278
  structuralOnly,
3084
3279
  tablesAddressedBy
package/dist/index.d.cts CHANGED
@@ -820,6 +820,90 @@ 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
+ interface RenameResult {
883
+ readonly text: string;
884
+ readonly rewritten: number;
885
+ /**
886
+ * Names the table knows only per entry, imported from somewhere the table
887
+ * does not cover. Reported rather than guessed: which registry the
888
+ * consumer meant is not in the text.
889
+ */
890
+ readonly ambiguous: readonly string[];
891
+ }
892
+ /**
893
+ * The specifier and the bound names of every `import { … } from '…'`.
894
+ *
895
+ * Read backwards from each `from`, one character at a time, instead of with
896
+ * one regular expression over the statement: `\{([^}]*)\}\s+from` and its
897
+ * siblings backtrack quadratically on a file full of `import {{`, and the
898
+ * file is a consumer's — whatever they wrote, this must finish.
899
+ */
900
+ declare function namedImports(text: string): Array<{
901
+ names: string[];
902
+ specifier: string;
903
+ }>;
904
+ /** Applies the table to one file's text. Idempotent: a second run changes nothing. */
905
+ declare function rewriteNames(text: string, table: RenameTable): RenameResult;
906
+
823
907
  interface PatchAppModuleOptions {
824
908
  /** Import specifier for the persistence bundle, or null when not generated. */
825
909
  persistenceImport: string | null;
@@ -1063,4 +1147,4 @@ declare class UserCommands extends CommandRunner {
1063
1147
  parsePassword(val: string): string;
1064
1148
  }
1065
1149
 
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 };
1150
+ 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, 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, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
package/dist/index.d.ts CHANGED
@@ -820,6 +820,90 @@ 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
+ interface RenameResult {
883
+ readonly text: string;
884
+ readonly rewritten: number;
885
+ /**
886
+ * Names the table knows only per entry, imported from somewhere the table
887
+ * does not cover. Reported rather than guessed: which registry the
888
+ * consumer meant is not in the text.
889
+ */
890
+ readonly ambiguous: readonly string[];
891
+ }
892
+ /**
893
+ * The specifier and the bound names of every `import { … } from '…'`.
894
+ *
895
+ * Read backwards from each `from`, one character at a time, instead of with
896
+ * one regular expression over the statement: `\{([^}]*)\}\s+from` and its
897
+ * siblings backtrack quadratically on a file full of `import {{`, and the
898
+ * file is a consumer's — whatever they wrote, this must finish.
899
+ */
900
+ declare function namedImports(text: string): Array<{
901
+ names: string[];
902
+ specifier: string;
903
+ }>;
904
+ /** Applies the table to one file's text. Idempotent: a second run changes nothing. */
905
+ declare function rewriteNames(text: string, table: RenameTable): RenameResult;
906
+
823
907
  interface PatchAppModuleOptions {
824
908
  /** Import specifier for the persistence bundle, or null when not generated. */
825
909
  persistenceImport: string | null;
@@ -1063,4 +1147,4 @@ declare class UserCommands extends CommandRunner {
1063
1147
  parsePassword(val: string): string;
1064
1148
  }
1065
1149
 
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 };
1150
+ 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, 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, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
- // src/tokens.ts
5
- var CLI_CONTEXT_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/Config");
6
- var USER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/UserPort");
7
- var USER_MANAGEMENT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/UserManagementPort");
8
- var AUDIT_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/AuditQueryPort");
9
- var DOCTOR_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/DoctorChecks");
10
- var MANIFEST_ACCESS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/ManifestAccessPort");
11
- var MANIFEST_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-cli/ManifestChecks");
4
+ // src/cli.tokens.ts
5
+ var CLI_CONTEXT_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/Config");
6
+ var USER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/UserPort");
7
+ var USER_MANAGEMENT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/UserManagementPort");
8
+ var AUDIT_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/AuditQueryPort");
9
+ var DOCTOR_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/DoctorChecks");
10
+ var MANIFEST_ACCESS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/ManifestAccessPort");
11
+ var MANIFEST_CHECKS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/cli/ManifestChecks");
12
12
 
13
13
  // src/cli-context.service.ts
14
14
  import * as os from "os";
@@ -1716,6 +1716,187 @@ function patchOptionsFor(plan) {
1716
1716
  }
1717
1717
  __name(patchOptionsFor, "patchOptionsFor");
1718
1718
 
1719
+ // src/codemods/v1-imports.ts
1720
+ var PUBLIC_PREFIXES = [
1721
+ "ui/",
1722
+ "layouts/",
1723
+ "auth/",
1724
+ "pages/"
1725
+ ];
1726
+ function buildImportMap(table) {
1727
+ const map = /* @__PURE__ */ new Map();
1728
+ for (const [from, to] of Object.entries(table.moves)) {
1729
+ const onSurface = PUBLIC_PREFIXES.some((prefix) => to.startsWith(prefix));
1730
+ if (!onSurface && !to.startsWith("@")) continue;
1731
+ if (from.startsWith("components/")) {
1732
+ map.set(from, to);
1733
+ continue;
1734
+ }
1735
+ if (!from.startsWith("pages-standard/")) continue;
1736
+ const file = from.slice("pages-standard/".length);
1737
+ if (file.includes("/")) continue;
1738
+ map.set(from, to);
1739
+ map.set(`pages/${file}`, to);
1740
+ }
1741
+ for (const [from, to] of Object.entries(table.packages ?? {})) {
1742
+ if (from === "_") continue;
1743
+ map.set(from, to);
1744
+ }
1745
+ for (const [from, to] of Object.entries(table.moveDirectories ?? {})) {
1746
+ map.set(`${from}/`, `${to}/`);
1747
+ }
1748
+ return map;
1749
+ }
1750
+ __name(buildImportMap, "buildImportMap");
1751
+ function wentPrivate(map, subpath) {
1752
+ for (const [prefix, target] of map) {
1753
+ if (prefix.endsWith("/") && target.startsWith("internal/") && subpath.startsWith(prefix)) {
1754
+ return true;
1755
+ }
1756
+ }
1757
+ return false;
1758
+ }
1759
+ __name(wentPrivate, "wentPrivate");
1760
+ function rewriteSubpath(map, subpath) {
1761
+ const direct = map.get(subpath);
1762
+ if (direct !== void 0 && direct !== subpath) return direct;
1763
+ for (const [prefix, target] of map) {
1764
+ if (prefix.endsWith("/") && target.startsWith("@") && subpath.startsWith(prefix)) {
1765
+ return `${target}${subpath.slice(prefix.length)}`;
1766
+ }
1767
+ }
1768
+ if (subpath.startsWith("pages-standard/") && !wentPrivate(map, subpath)) {
1769
+ const file = subpath.slice("pages-standard/".length);
1770
+ if (!file.includes("/")) return `pages/${file}`;
1771
+ }
1772
+ return null;
1773
+ }
1774
+ __name(rewriteSubpath, "rewriteSubpath");
1775
+ function isNoLongerPublic(map, subpath) {
1776
+ if (subpath.startsWith("components/")) return !map.has(subpath);
1777
+ return subpath.startsWith("pages-standard/") && wentPrivate(map, subpath);
1778
+ }
1779
+ __name(isNoLongerPublic, "isNoLongerPublic");
1780
+ var UI_VUE_SPECIFIER = /@saasicat\/ui-vue\/([A-Za-z0-9/_.-]+)/g;
1781
+ function rewriteImports(text, map) {
1782
+ const unmapped = /* @__PURE__ */ new Map();
1783
+ let rewritten = 0;
1784
+ const next = text.replace(UI_VUE_SPECIFIER, (whole, subpath) => {
1785
+ const to = rewriteSubpath(map, subpath);
1786
+ if (to === null) {
1787
+ if (isNoLongerPublic(map, subpath)) {
1788
+ unmapped.set(subpath, (unmapped.get(subpath) ?? 0) + 1);
1789
+ }
1790
+ return whole;
1791
+ }
1792
+ rewritten += 1;
1793
+ return to.startsWith("@") ? to : `@saasicat/ui-vue/${to}`;
1794
+ });
1795
+ return {
1796
+ text: next,
1797
+ rewritten,
1798
+ unmapped
1799
+ };
1800
+ }
1801
+ __name(rewriteImports, "rewriteImports");
1802
+
1803
+ // src/codemods/v1-rename.ts
1804
+ var escape = /* @__PURE__ */ __name((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"), "escape");
1805
+ var FROM_SPECIFIER = /from\s*(['"])([^'"]+)\1/g;
1806
+ var isSpace = /* @__PURE__ */ __name((ch) => ch === " " || ch === " " || ch === "\n" || ch === "\r", "isSpace");
1807
+ function namedImports(text) {
1808
+ const found = [];
1809
+ for (const match of text.matchAll(FROM_SPECIFIER)) {
1810
+ let i = (match.index ?? 0) - 1;
1811
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1812
+ if (text[i] !== "}") continue;
1813
+ const close = i;
1814
+ const open = text.lastIndexOf("{", close);
1815
+ if (open < 0) continue;
1816
+ i = open - 1;
1817
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1818
+ let head = text.slice(Math.max(0, i - 3), i + 1);
1819
+ if (head === "type") {
1820
+ i -= 4;
1821
+ while (i >= 0 && isSpace(text[i])) i -= 1;
1822
+ head = text.slice(Math.max(0, i - 5), i + 1);
1823
+ } else {
1824
+ head = text.slice(Math.max(0, i - 5), i + 1);
1825
+ }
1826
+ if (head !== "import") continue;
1827
+ const names = text.slice(open + 1, close).split(",").map((raw) => {
1828
+ const words = raw.trim().split(/\s+/);
1829
+ if (words[0] === "type") words.shift();
1830
+ return words[0] ?? "";
1831
+ }).filter((name) => name.length > 0);
1832
+ found.push({
1833
+ names,
1834
+ specifier: match[2]
1835
+ });
1836
+ }
1837
+ return found;
1838
+ }
1839
+ __name(namedImports, "namedImports");
1840
+ function rewriteNames(text, table) {
1841
+ let next = text;
1842
+ let rewritten = 0;
1843
+ const ambiguous = /* @__PURE__ */ new Set();
1844
+ const perEntry = /* @__PURE__ */ new Map();
1845
+ for (const { names, specifier } of namedImports(text)) {
1846
+ const mapping = table.entryTokens[specifier];
1847
+ for (const name of names) {
1848
+ const knownSomewhere = Object.values(table.entryTokens).some((m) => name in m);
1849
+ if (!knownSomewhere) continue;
1850
+ const already = perEntry.get(name);
1851
+ if (mapping && name in mapping && (already === void 0 || already === mapping[name])) {
1852
+ perEntry.set(name, mapping[name]);
1853
+ } else {
1854
+ if (already !== void 0) perEntry.delete(name);
1855
+ ambiguous.add(`${name} from '${specifier}'`);
1856
+ }
1857
+ }
1858
+ }
1859
+ for (const [from, to] of perEntry) {
1860
+ next = next.replace(new RegExp(`\\b${escape(from)}\\b`, "g"), () => {
1861
+ rewritten += 1;
1862
+ return to;
1863
+ });
1864
+ }
1865
+ for (const [from, to] of Object.entries(table.identifierStems)) {
1866
+ next = next.replace(new RegExp(escape(from), "g"), () => {
1867
+ rewritten += 1;
1868
+ return to;
1869
+ });
1870
+ }
1871
+ for (const [from, to] of Object.entries(table.registryKeys)) {
1872
+ const symbolFor = new RegExp(`(Symbol\\.for\\(\\s*['"\`])${escape(from)}`, "g");
1873
+ next = next.replace(symbolFor, (_, head) => {
1874
+ rewritten += 1;
1875
+ return `${head}${to}`;
1876
+ });
1877
+ if (from.startsWith("@")) continue;
1878
+ const literal = new RegExp(`(['"\`])${escape(from)}`, "g");
1879
+ next = next.replace(literal, (_, quote) => {
1880
+ rewritten += 1;
1881
+ return `${quote}${to}`;
1882
+ });
1883
+ }
1884
+ for (const [from, to] of Object.entries(table.subpaths)) {
1885
+ next = next.replace(new RegExp(escape(from), "g"), () => {
1886
+ rewritten += 1;
1887
+ return to;
1888
+ });
1889
+ }
1890
+ return {
1891
+ text: next,
1892
+ rewritten,
1893
+ ambiguous: [
1894
+ ...ambiguous
1895
+ ].sort()
1896
+ };
1897
+ }
1898
+ __name(rewriteNames, "rewriteNames");
1899
+
1719
1900
  // src/init/patch-app-module.ts
1720
1901
  var MARKER = "SaaSiCatModule.forRoot";
1721
1902
  function patchAppModule(source, options) {
@@ -1840,7 +2021,7 @@ var LIMIT_FILTER_IMPORTS = [
1840
2021
  "import { LimitExceededFilter } from '@saasicat/nest/billing';"
1841
2022
  ].join("\n");
1842
2023
 
1843
- // src/module.ts
2024
+ // src/cli-context.module.ts
1844
2025
  import { Module } from "@nestjs/common";
1845
2026
  import { asProvider } from "@saasicat/nest";
1846
2027
  function _ts_decorate8(decorators, target, key, desc) {
@@ -2925,6 +3106,7 @@ export {
2925
3106
  MfaSetupFlow,
2926
3107
  PLATFORM_DOCTOR_CHECK_PROVIDERS,
2927
3108
  PlanCatalogDoctorCheck,
3109
+ UI_VUE_SPECIFIER,
2928
3110
  USER_MANAGEMENT_PORT_TOKEN,
2929
3111
  USER_PORT_TOKEN,
2930
3112
  UserCommands,
@@ -2939,6 +3121,7 @@ export {
2939
3121
  blankStringLiterals,
2940
3122
  blockBodyLines,
2941
3123
  breaksContract,
3124
+ buildImportMap,
2942
3125
  checkSchema,
2943
3126
  constraintsFor,
2944
3127
  enableFkPointers,
@@ -2950,10 +3133,12 @@ export {
2950
3133
  foreignKeyOf,
2951
3134
  hasBackRelation,
2952
3135
  hasConstraints,
3136
+ isNoLongerPublic,
2953
3137
  isOneToOne,
2954
3138
  kebabCase,
2955
3139
  migrationCreatedBy,
2956
3140
  minimumQuotasPerPlan,
3141
+ namedImports,
2957
3142
  parseBlockAttributes,
2958
3143
  parseEnumValues,
2959
3144
  parseFields,
@@ -2967,6 +3152,9 @@ export {
2967
3152
  quotaKeyPattern,
2968
3153
  relationNameOf,
2969
3154
  reportConstraints,
3155
+ rewriteImports,
3156
+ rewriteNames,
3157
+ rewriteSubpath,
2970
3158
  stripLineComment,
2971
3159
  structuralOnly,
2972
3160
  tablesAddressedBy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasicat/cli",
3
- "version": "0.27.0",
3
+ "version": "1.0.0-rc.0",
4
4
  "description": "CLI helpers for SaaS platform consumers. Provides CliContextService (identity, MFA, production confirm, audit tag).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -21,16 +21,17 @@
21
21
  "files": [
22
22
  "dist",
23
23
  "bin",
24
- "templates"
24
+ "templates",
25
+ "codemods"
25
26
  ],
26
27
  "bin": {
27
28
  "saasicat": "./bin/saasicat.js"
28
29
  },
29
30
  "dependencies": {
30
31
  "qrcode-terminal": "^0.12.0",
31
- "@saasicat/nest": "^0.27.0",
32
- "@saasicat/spec": "^0.27.0",
33
- "@saasicat/types": "^0.27.0"
32
+ "@saasicat/nest": "^1.0.0-rc.0",
33
+ "@saasicat/spec": "^1.0.0-rc.0",
34
+ "@saasicat/types": "^1.0.0-rc.0"
34
35
  },
35
36
  "peerDependencies": {
36
37
  "@nestjs/common": "^11.0.0",
@@ -49,7 +50,7 @@
49
50
  "repository": {
50
51
  "type": "git",
51
52
  "url": "https://github.com/uelker70/saasicat.git",
52
- "directory": "packages/saas-platform-cli"
53
+ "directory": "packages/cli"
53
54
  },
54
55
  "bugs": {
55
56
  "url": "https://github.com/uelker70/saasicat/issues"
@@ -60,6 +61,6 @@
60
61
  "scripts": {
61
62
  "build": "node ../../scripts/build-and-prune.mjs \"tsup src/index.ts --format esm,cjs --dts --external @saasicat/types --external @saasicat/nest --external @nestjs/common --external nest-commander --external qrcode-terminal\"",
62
63
  "pretest": "pnpm run build",
63
- "test": "node --test tests/**/*.test.js"
64
+ "test": "node --test 'tests/*.test.js'"
64
65
  }
65
66
  }