@saasicat/cli 1.0.0-rc.0 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/saasicat.js +105 -17
- package/codemods/v1-rename.map.json +6 -0
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +175 -8
- package/dist/index.d.cts +85 -8
- package/dist/index.d.ts +85 -8
- package/dist/index.js +165 -4
- package/package.json +7 -6
- package/templates/init/src/auth/password.hasher.ts.tpl +1 -1
- package/templates/init/src/saas/admin-manifest.contribution.ts.tpl +1 -1
- package/templates/init/src/saas/feature-ui-registry.ts.tpl +1 -1
- package/templates/init/src/saas/quota.provider.ts.tpl +1 -1
package/bin/saasicat.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
|
|
29
29
|
import { existsSync } from 'node:fs';
|
|
30
|
-
import { dirname, join, resolve } from 'node:path';
|
|
30
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
31
31
|
import { createRequire } from 'node:module';
|
|
32
32
|
import { fileURLToPath } from 'node:url';
|
|
33
33
|
import { spawn } from 'node:child_process';
|
|
@@ -43,13 +43,16 @@ import {
|
|
|
43
43
|
applyTokens,
|
|
44
44
|
constraintsFor,
|
|
45
45
|
checkSchema,
|
|
46
|
-
|
|
46
|
+
extractFragmentBlocks,
|
|
47
47
|
findFkPointers,
|
|
48
48
|
hasConstraints,
|
|
49
49
|
assertValidProjectKey,
|
|
50
|
+
judgeModuleResolution,
|
|
51
|
+
readEffectiveModuleResolution,
|
|
50
52
|
buildImportMap,
|
|
51
53
|
migrationCreatedBy,
|
|
52
54
|
rewriteImports,
|
|
55
|
+
rewriteManifest,
|
|
53
56
|
rewriteNames,
|
|
54
57
|
reportConstraints,
|
|
55
58
|
patchAppModule,
|
|
@@ -132,14 +135,15 @@ async function selectFragmentFiles(dir, filter) {
|
|
|
132
135
|
|
|
133
136
|
async function loadFragments(dir, filter) {
|
|
134
137
|
const selected = await selectFragmentFiles(dir, filter);
|
|
135
|
-
const blocks = new Map();
|
|
138
|
+
const blocks = { enums: new Map(), models: new Map() };
|
|
136
139
|
for (const file of selected) {
|
|
137
140
|
const content = await readFile(join(dir, file), 'utf8');
|
|
138
|
-
const fileBlocks =
|
|
139
|
-
for (const [name, body] of fileBlocks) {
|
|
140
|
-
if (!blocks.has(name))
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const fileBlocks = extractFragmentBlocks(content);
|
|
142
|
+
for (const [name, body] of fileBlocks.enums) {
|
|
143
|
+
if (!blocks.enums.has(name)) blocks.enums.set(name, body);
|
|
144
|
+
}
|
|
145
|
+
for (const [name, body] of fileBlocks.models) {
|
|
146
|
+
if (!blocks.models.has(name)) blocks.models.set(name, body);
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
return { files: selected, blocks };
|
|
@@ -171,7 +175,7 @@ async function cmdSchemaApply(args) {
|
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
const { files, blocks } = await loadFragments(fragmentsDir, filter);
|
|
174
|
-
if (blocks.size === 0) {
|
|
178
|
+
if (blocks.models.size === 0) {
|
|
175
179
|
console.error('✗ No models found in the selected fragments.');
|
|
176
180
|
process.exit(1);
|
|
177
181
|
}
|
|
@@ -185,13 +189,16 @@ async function cmdSchemaApply(args) {
|
|
|
185
189
|
// one where the manual step is most likely to have been forgotten.
|
|
186
190
|
const fk = resolveFkPointers(result.schema, args);
|
|
187
191
|
|
|
188
|
-
if (result.added.length === 0 && fk.enabled.length === 0) {
|
|
192
|
+
if (result.added.length === 0 && result.addedEnums.length === 0 && fk.enabled.length === 0) {
|
|
189
193
|
console.log(`→ Nothing to do. Models already present: ${result.skipped.join(', ')}`);
|
|
190
194
|
reportFkPointers(fk, args);
|
|
191
195
|
return;
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
if (args['dry-run']) {
|
|
199
|
+
if (result.addedEnums.length) {
|
|
200
|
+
console.log(`(--dry-run) Would append enums: ${result.addedEnums.join(', ')}`);
|
|
201
|
+
}
|
|
195
202
|
if (result.added.length) {
|
|
196
203
|
console.log(`(--dry-run) Would append: ${result.added.join(', ')}`);
|
|
197
204
|
}
|
|
@@ -209,12 +216,18 @@ async function cmdSchemaApply(args) {
|
|
|
209
216
|
for (const { line } of fk.enabled) {
|
|
210
217
|
console.log(` ${line + 1}: ${fk.schema.split('\n')[line]?.trim() ?? ''}`);
|
|
211
218
|
}
|
|
212
|
-
|
|
213
|
-
if (
|
|
219
|
+
const appended = result.added.length > 0 || result.addedEnums.length > 0;
|
|
220
|
+
if (fk.enabled.length > 0 && appended) console.log('');
|
|
221
|
+
if (appended) console.log(result.schema.slice(schema.length));
|
|
214
222
|
return;
|
|
215
223
|
}
|
|
216
224
|
|
|
217
225
|
await writeFile(schemaPath, fk.schema, 'utf8');
|
|
226
|
+
if (result.addedEnums.length) {
|
|
227
|
+
console.log(
|
|
228
|
+
`✓ Appended ${result.addedEnums.length} enum(s): ${result.addedEnums.join(', ')}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
218
231
|
console.log(`✓ Appended ${result.added.length} model(s): ${result.added.join(', ')}`);
|
|
219
232
|
if (result.skipped.length) {
|
|
220
233
|
console.log(`→ Skipped (already present): ${result.skipped.join(', ')}`);
|
|
@@ -548,6 +561,7 @@ async function cmdCodemodV1Imports(args) {
|
|
|
548
561
|
let rewritten = 0;
|
|
549
562
|
let touched = 0;
|
|
550
563
|
await walkSources(root, async (full, source) => {
|
|
564
|
+
if (basename(full) === CODEMOD_MANIFEST) return;
|
|
551
565
|
const result = rewriteImports(source, map);
|
|
552
566
|
for (const [subpath, n] of result.unmapped) {
|
|
553
567
|
unmapped.set(subpath, (unmapped.get(subpath) ?? 0) + n);
|
|
@@ -592,8 +606,15 @@ async function cmdCodemodV1Rename(args) {
|
|
|
592
606
|
const ambiguous = new Map();
|
|
593
607
|
let rewritten = 0;
|
|
594
608
|
let touched = 0;
|
|
609
|
+
let manifestsTouched = 0;
|
|
595
610
|
await walkSources(root, async (full, source) => {
|
|
596
|
-
|
|
611
|
+
// A manifest takes the package renames in its dependency fields; a
|
|
612
|
+
// source file takes everything. Under pnpm an import a manifest does
|
|
613
|
+
// not declare fails to resolve, so the two travel together.
|
|
614
|
+
const result =
|
|
615
|
+
basename(full) === CODEMOD_MANIFEST
|
|
616
|
+
? rewriteManifest(source, table, { targetRange: `^${OWN_VERSION}` })
|
|
617
|
+
: rewriteNames(source, table);
|
|
597
618
|
for (const name of result.ambiguous) {
|
|
598
619
|
ambiguous.set(name, (ambiguous.get(name) ?? 0) + 1);
|
|
599
620
|
}
|
|
@@ -601,11 +622,26 @@ async function cmdCodemodV1Rename(args) {
|
|
|
601
622
|
if (!dryRun) await writeFile(full, result.text);
|
|
602
623
|
rewritten += result.rewritten;
|
|
603
624
|
touched += 1;
|
|
625
|
+
if (basename(full) === CODEMOD_MANIFEST) manifestsTouched += 1;
|
|
604
626
|
});
|
|
605
627
|
|
|
606
628
|
console.log(
|
|
607
629
|
`${dryRun ? 'Would rewrite' : 'Rewrote'} ${rewritten} name(s) in ${touched} file(s).`,
|
|
608
630
|
);
|
|
631
|
+
if (manifestsTouched > 0) {
|
|
632
|
+
// The lockfile is not rewritten: its shape is the package manager's,
|
|
633
|
+
// and a wrong guess at it is worse than an honest instruction. A CI
|
|
634
|
+
// that installs with a frozen lockfile refuses the migrated checkout
|
|
635
|
+
// until it is regenerated.
|
|
636
|
+
console.log('');
|
|
637
|
+
console.log(
|
|
638
|
+
`${manifestsTouched} package.json ${manifestsTouched === 1 ? 'file' : 'files'} changed — ` +
|
|
639
|
+
'regenerate the lockfile before committing:',
|
|
640
|
+
);
|
|
641
|
+
console.log(
|
|
642
|
+
' pnpm install (or npm install / yarn install, whichever owns your lockfile)',
|
|
643
|
+
);
|
|
644
|
+
}
|
|
609
645
|
if (ambiguous.size === 0) return;
|
|
610
646
|
|
|
611
647
|
console.log('');
|
|
@@ -617,8 +653,20 @@ async function cmdCodemodV1Rename(args) {
|
|
|
617
653
|
console.log(' FEATURE_UI_REGISTRY_TOKEN meant one registry in `@saasicat/nest/billing`');
|
|
618
654
|
console.log(' and another in `@saasicat/nest/catalog`. Import it from the entry you');
|
|
619
655
|
console.log(' mean — BILLING_FEATURE_UI_REGISTRY_TOKEN or CATALOG_FEATURE_UI_REGISTRY_TOKEN.');
|
|
656
|
+
console.log(' A dependency listed "in <field> (<range>)" points at a workspace or a path;');
|
|
657
|
+
console.log(' rename it by hand to @saasicat/core at the location you keep it.');
|
|
620
658
|
}
|
|
621
659
|
|
|
660
|
+
/**
|
|
661
|
+
* The version this CLI was released as — and therefore the line a consumer
|
|
662
|
+
* running its codemod is migrating to. The manifest rewrite sets the renamed
|
|
663
|
+
* dependency to `^<this>`, because the old range (`^0.27.0`) names a line
|
|
664
|
+
* the renamed package was never published on.
|
|
665
|
+
*/
|
|
666
|
+
const OWN_VERSION = JSON.parse(
|
|
667
|
+
await readFile(join(dirname(require_.resolve('@saasicat/cli')), '..', 'package.json'), 'utf8'),
|
|
668
|
+
).version;
|
|
669
|
+
|
|
622
670
|
/** Where a shipped codemod table lives, resolved through the package itself. */
|
|
623
671
|
function codemodTable(name) {
|
|
624
672
|
return join(dirname(require_.resolve('@saasicat/cli')), '..', 'codemods', name);
|
|
@@ -630,6 +678,8 @@ function codemodTable(name) {
|
|
|
630
678
|
const CODEMOD_SKIP = new Set(['node_modules', '.git', '.output', 'coverage']);
|
|
631
679
|
const isBuildOutput = (name) => name === 'dist' || name.startsWith('dist-');
|
|
632
680
|
const CODEMOD_EXTENSIONS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue|md)$/;
|
|
681
|
+
/** Walked for the package renames alone; see `rewriteManifest`. */
|
|
682
|
+
const CODEMOD_MANIFEST = 'package.json';
|
|
633
683
|
|
|
634
684
|
/** Every source file under `root` a codemod may touch, with its text. */
|
|
635
685
|
async function walkSources(root, visit) {
|
|
@@ -641,7 +691,7 @@ async function walkSources(root, visit) {
|
|
|
641
691
|
await walk(full);
|
|
642
692
|
continue;
|
|
643
693
|
}
|
|
644
|
-
if (!CODEMOD_EXTENSIONS.test(entry.name)) continue;
|
|
694
|
+
if (!CODEMOD_EXTENSIONS.test(entry.name) && entry.name !== CODEMOD_MANIFEST) continue;
|
|
645
695
|
await visit(full, await readFile(full, 'utf8'));
|
|
646
696
|
}
|
|
647
697
|
};
|
|
@@ -673,6 +723,23 @@ async function cmdInit(args, argv) {
|
|
|
673
723
|
}
|
|
674
724
|
|
|
675
725
|
const root = resolve(args.dir ?? '.');
|
|
726
|
+
|
|
727
|
+
// Before anything is written: the files below import subpath exports,
|
|
728
|
+
// and under `moduleResolution: node` none of them resolves. Found by
|
|
729
|
+
// running the quickstart against an app that predates `nodenext`.
|
|
730
|
+
if (existsSync(join(root, 'tsconfig.json'))) {
|
|
731
|
+
const ts = loadTypeScript(root);
|
|
732
|
+
if (ts === null) {
|
|
733
|
+
console.log('! tsconfig.json not checked: no TypeScript resolvable from this project.');
|
|
734
|
+
} else {
|
|
735
|
+
const verdict = judgeModuleResolution(readEffectiveModuleResolution(root, ts));
|
|
736
|
+
if (!verdict.ok) {
|
|
737
|
+
console.error(`✗ ${verdict.reason}`);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
676
743
|
const plan = planInit({
|
|
677
744
|
projectKey: args['project-key'],
|
|
678
745
|
appName: args['app-name'],
|
|
@@ -734,12 +801,33 @@ async function cmdInit(args, argv) {
|
|
|
734
801
|
console.log(' — the generated app does NOT compile until you do. An empty');
|
|
735
802
|
console.log(' array means "deliberately auth-free" to the platform, and');
|
|
736
803
|
console.log(' would publish GET /admin/discovery to anyone who asks.');
|
|
804
|
+
console.log(' 2. Name the modules in `imports: [YourPrismaModule, YourAuthModule]`');
|
|
805
|
+
console.log(' — the one exporting PrismaService and the one your guard needs.');
|
|
806
|
+
console.log(' The platform module resolves its providers from that list;');
|
|
807
|
+
console.log(' without it the first boot stops at "Nest can\'t resolve');
|
|
808
|
+
console.log(' dependencies of … (PrismaService)".');
|
|
737
809
|
if (plan.quotaProviders.length > 0) {
|
|
738
|
-
console.log('
|
|
739
|
-
console.log('
|
|
810
|
+
console.log(' 3. Check each quota provider counts the right thing');
|
|
811
|
+
console.log(' 4. saasicat schema migrate --name=add_saasicat');
|
|
740
812
|
} else {
|
|
741
|
-
console.log('
|
|
813
|
+
console.log(' 3. saasicat schema migrate --name=add_saasicat');
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* The TypeScript that will compile the project: the consumer's own, resolved
|
|
819
|
+
* from the project root, so the tsconfig is read the way the build reads it.
|
|
820
|
+
* Falls back to the one this CLI was installed with, then to null.
|
|
821
|
+
*/
|
|
822
|
+
function loadTypeScript(root) {
|
|
823
|
+
for (const from of [join(root, 'package.json'), import.meta.url]) {
|
|
824
|
+
try {
|
|
825
|
+
return createRequire(from)('typescript');
|
|
826
|
+
} catch {
|
|
827
|
+
// Not resolvable from here — try the next origin.
|
|
828
|
+
}
|
|
742
829
|
}
|
|
830
|
+
return null;
|
|
743
831
|
}
|
|
744
832
|
|
|
745
833
|
/** Adds the platform to `src/app.module.ts`, or says exactly what to paste. */
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"consumer that spelled a key themselves gets the same symbol the platform",
|
|
16
16
|
"now registers; without this their injection would resolve to nothing.",
|
|
17
17
|
"",
|
|
18
|
+
"`packages` renames a whole package — in specifiers and in package.json.",
|
|
19
|
+
"",
|
|
18
20
|
"`entryTokens` is keyed by the import specifier, because the old name",
|
|
19
21
|
"meant different registries in different entries. An import of it from",
|
|
20
22
|
"anywhere else is reported, not guessed."
|
|
@@ -41,5 +43,9 @@
|
|
|
41
43
|
},
|
|
42
44
|
"subpaths": {
|
|
43
45
|
"@saasicat/ui-vue/testing-e2e/": "@saasicat/ui-vue/testing/"
|
|
46
|
+
},
|
|
47
|
+
"packages": {
|
|
48
|
+
"_": "A package that was renamed. Applied to import specifiers AND to the dependency fields of every package.json the walk meets: an import rewritten without its manifest does not resolve under pnpm.",
|
|
49
|
+
"@saasicat/types": "@saasicat/core"
|
|
44
50
|
}
|
|
45
51
|
}
|
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d16106f714ce74c061cc0211a2be4ebe01a1cb80b8e4b5da45ff296c861cff71
|
package/dist/index.cjs
CHANGED
|
@@ -85,6 +85,9 @@ __export(index_exports, {
|
|
|
85
85
|
enableFkPointers: () => enableFkPointers,
|
|
86
86
|
extractBlockNames: () => extractBlockNames,
|
|
87
87
|
extractBlocks: () => extractBlocks,
|
|
88
|
+
extractEnumBlocks: () => extractEnumBlocks,
|
|
89
|
+
extractEnumNames: () => extractEnumNames,
|
|
90
|
+
extractFragmentBlocks: () => extractFragmentBlocks,
|
|
88
91
|
extractModelBlocks: () => extractModelBlocks,
|
|
89
92
|
extractModelNames: () => extractModelNames,
|
|
90
93
|
findFkPointers: () => findFkPointers,
|
|
@@ -93,6 +96,7 @@ __export(index_exports, {
|
|
|
93
96
|
hasConstraints: () => hasConstraints,
|
|
94
97
|
isNoLongerPublic: () => isNoLongerPublic,
|
|
95
98
|
isOneToOne: () => isOneToOne,
|
|
99
|
+
judgeModuleResolution: () => judgeModuleResolution,
|
|
96
100
|
kebabCase: () => kebabCase,
|
|
97
101
|
migrationCreatedBy: () => migrationCreatedBy,
|
|
98
102
|
minimumQuotasPerPlan: () => minimumQuotasPerPlan,
|
|
@@ -108,9 +112,11 @@ __export(index_exports, {
|
|
|
108
112
|
planInit: () => planInit,
|
|
109
113
|
projectKeyPattern: () => projectKeyPattern,
|
|
110
114
|
quotaKeyPattern: () => quotaKeyPattern,
|
|
115
|
+
readEffectiveModuleResolution: () => readEffectiveModuleResolution,
|
|
111
116
|
relationNameOf: () => relationNameOf,
|
|
112
117
|
reportConstraints: () => reportConstraints,
|
|
113
118
|
rewriteImports: () => rewriteImports,
|
|
119
|
+
rewriteManifest: () => rewriteManifest,
|
|
114
120
|
rewriteNames: () => rewriteNames,
|
|
115
121
|
rewriteSubpath: () => rewriteSubpath,
|
|
116
122
|
stripLineComment: () => stripLineComment,
|
|
@@ -1247,13 +1253,43 @@ function extractModelBlocks(fragment) {
|
|
|
1247
1253
|
return extractBlocks(fragment, "model");
|
|
1248
1254
|
}
|
|
1249
1255
|
__name(extractModelBlocks, "extractModelBlocks");
|
|
1256
|
+
function extractEnumBlocks(fragment) {
|
|
1257
|
+
return extractBlocks(fragment, "enum");
|
|
1258
|
+
}
|
|
1259
|
+
__name(extractEnumBlocks, "extractEnumBlocks");
|
|
1260
|
+
function extractEnumNames(schema2) {
|
|
1261
|
+
return extractBlockNames(schema2, "enum");
|
|
1262
|
+
}
|
|
1263
|
+
__name(extractEnumNames, "extractEnumNames");
|
|
1264
|
+
function extractFragmentBlocks(fragment) {
|
|
1265
|
+
return {
|
|
1266
|
+
enums: extractEnumBlocks(fragment),
|
|
1267
|
+
models: extractModelBlocks(fragment)
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
__name(extractFragmentBlocks, "extractFragmentBlocks");
|
|
1250
1271
|
function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
|
|
1251
|
-
const
|
|
1272
|
+
const blocks = fragmentBlocks instanceof Map ? {
|
|
1273
|
+
enums: /* @__PURE__ */ new Map(),
|
|
1274
|
+
models: fragmentBlocks
|
|
1275
|
+
} : fragmentBlocks;
|
|
1276
|
+
const existingModels = new Set(extractModelNames(schema2));
|
|
1277
|
+
const existingEnums = new Set(extractEnumNames(schema2));
|
|
1252
1278
|
const added = [];
|
|
1253
1279
|
const skipped = [];
|
|
1280
|
+
const addedEnums = [];
|
|
1281
|
+
const skippedEnums = [];
|
|
1254
1282
|
const additions = [];
|
|
1255
|
-
for (const [name, block] of
|
|
1256
|
-
if (
|
|
1283
|
+
for (const [name, block] of blocks.enums) {
|
|
1284
|
+
if (existingEnums.has(name)) {
|
|
1285
|
+
skippedEnums.push(name);
|
|
1286
|
+
} else {
|
|
1287
|
+
addedEnums.push(name);
|
|
1288
|
+
additions.push(block);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
for (const [name, block] of blocks.models) {
|
|
1292
|
+
if (existingModels.has(name)) {
|
|
1257
1293
|
skipped.push(name);
|
|
1258
1294
|
} else {
|
|
1259
1295
|
added.push(name);
|
|
@@ -1264,6 +1300,8 @@ function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
|
|
|
1264
1300
|
return {
|
|
1265
1301
|
added,
|
|
1266
1302
|
skipped,
|
|
1303
|
+
addedEnums,
|
|
1304
|
+
skippedEnums,
|
|
1267
1305
|
schema: schema2
|
|
1268
1306
|
};
|
|
1269
1307
|
}
|
|
@@ -1280,6 +1318,8 @@ function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
|
|
|
1280
1318
|
return {
|
|
1281
1319
|
added,
|
|
1282
1320
|
skipped,
|
|
1321
|
+
addedEnums,
|
|
1322
|
+
skippedEnums,
|
|
1283
1323
|
schema: trimmedSchema + header + additions.join("\n\n") + "\n"
|
|
1284
1324
|
};
|
|
1285
1325
|
}
|
|
@@ -1834,6 +1874,38 @@ function patchOptionsFor(plan) {
|
|
|
1834
1874
|
}
|
|
1835
1875
|
__name(patchOptionsFor, "patchOptionsFor");
|
|
1836
1876
|
|
|
1877
|
+
// src/init/module-resolution.ts
|
|
1878
|
+
var import_node_path = require("path");
|
|
1879
|
+
var RESOLVES_SUBPATHS = /* @__PURE__ */ new Set([
|
|
1880
|
+
"node16",
|
|
1881
|
+
"nodenext",
|
|
1882
|
+
"bundler"
|
|
1883
|
+
]);
|
|
1884
|
+
function judgeModuleResolution(value) {
|
|
1885
|
+
if (value === null || RESOLVES_SUBPATHS.has(value)) {
|
|
1886
|
+
return {
|
|
1887
|
+
ok: true,
|
|
1888
|
+
value
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
return {
|
|
1892
|
+
ok: false,
|
|
1893
|
+
value,
|
|
1894
|
+
reason: `tsconfig.json resolves modules with "moduleResolution": "${value}"` + (value === "node10" ? ' (what TypeScript calls the "node" setting)' : "") + '. The files init writes import subpath exports (`@saasicat/nest/platform`, `/billing`, `/discovery`), which TypeScript resolves only under "node16", "nodenext" or "bundler". Set one of those first \u2014 `nest new` has used "nodenext" since NestJS 10.'
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
__name(judgeModuleResolution, "judgeModuleResolution");
|
|
1898
|
+
function readEffectiveModuleResolution(root, ts) {
|
|
1899
|
+
const configPath = (0, import_node_path.join)(root, "tsconfig.json");
|
|
1900
|
+
const read = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
1901
|
+
if (read.error || read.config === void 0) return null;
|
|
1902
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, root);
|
|
1903
|
+
const kind = parsed.options.moduleResolution;
|
|
1904
|
+
if (kind === void 0) return null;
|
|
1905
|
+
return ts.ModuleResolutionKind[kind].toLowerCase();
|
|
1906
|
+
}
|
|
1907
|
+
__name(readEffectiveModuleResolution, "readEffectiveModuleResolution");
|
|
1908
|
+
|
|
1837
1909
|
// src/codemods/v1-imports.ts
|
|
1838
1910
|
var PUBLIC_PREFIXES = [
|
|
1839
1911
|
"ui/",
|
|
@@ -1999,6 +2071,13 @@ function rewriteNames(text, table) {
|
|
|
1999
2071
|
return `${quote}${to}`;
|
|
2000
2072
|
});
|
|
2001
2073
|
}
|
|
2074
|
+
for (const [from, to] of Object.entries(table.packages ?? {})) {
|
|
2075
|
+
if (from === "_") continue;
|
|
2076
|
+
next = next.replace(new RegExp(`${escape(from)}(?=['"\`/])`, "g"), () => {
|
|
2077
|
+
rewritten += 1;
|
|
2078
|
+
return to;
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2002
2081
|
for (const [from, to] of Object.entries(table.subpaths)) {
|
|
2003
2082
|
next = next.replace(new RegExp(escape(from), "g"), () => {
|
|
2004
2083
|
rewritten += 1;
|
|
@@ -2014,6 +2093,81 @@ function rewriteNames(text, table) {
|
|
|
2014
2093
|
};
|
|
2015
2094
|
}
|
|
2016
2095
|
__name(rewriteNames, "rewriteNames");
|
|
2096
|
+
var DEPENDENCY_FIELDS = [
|
|
2097
|
+
"dependencies",
|
|
2098
|
+
"devDependencies",
|
|
2099
|
+
"peerDependencies",
|
|
2100
|
+
"optionalDependencies"
|
|
2101
|
+
];
|
|
2102
|
+
var UNTRANSLATABLE_RANGE = /^(workspace:|file:|link:|npm:|git\+|https?:)/;
|
|
2103
|
+
function rewriteManifest(text, table, options) {
|
|
2104
|
+
const renames = Object.entries(table.packages ?? {}).filter(([from]) => from !== "_");
|
|
2105
|
+
const ambiguous = [];
|
|
2106
|
+
let manifest;
|
|
2107
|
+
try {
|
|
2108
|
+
manifest = JSON.parse(text);
|
|
2109
|
+
} catch {
|
|
2110
|
+
return {
|
|
2111
|
+
text,
|
|
2112
|
+
rewritten: 0,
|
|
2113
|
+
ambiguous
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
let rewritten = 0;
|
|
2117
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
2118
|
+
const deps = manifest[field];
|
|
2119
|
+
if (!deps || typeof deps !== "object") continue;
|
|
2120
|
+
const entries = Object.entries(deps);
|
|
2121
|
+
const renamed = entries.map(([name, range]) => {
|
|
2122
|
+
const to = renames.find(([from]) => from === name)?.[1];
|
|
2123
|
+
if (!to) return [
|
|
2124
|
+
name,
|
|
2125
|
+
range
|
|
2126
|
+
];
|
|
2127
|
+
if (UNTRANSLATABLE_RANGE.test(range)) {
|
|
2128
|
+
ambiguous.push(`${name} in ${field} (${range})`);
|
|
2129
|
+
return [
|
|
2130
|
+
name,
|
|
2131
|
+
range
|
|
2132
|
+
];
|
|
2133
|
+
}
|
|
2134
|
+
rewritten += 1;
|
|
2135
|
+
return [
|
|
2136
|
+
to,
|
|
2137
|
+
options.targetRange
|
|
2138
|
+
];
|
|
2139
|
+
});
|
|
2140
|
+
manifest[field] = Object.fromEntries(renamed);
|
|
2141
|
+
}
|
|
2142
|
+
const meta = manifest.peerDependenciesMeta;
|
|
2143
|
+
if (meta && typeof meta === "object") {
|
|
2144
|
+
manifest.peerDependenciesMeta = Object.fromEntries(Object.entries(meta).map(([name, flags]) => {
|
|
2145
|
+
const to = renames.find(([from]) => from === name)?.[1];
|
|
2146
|
+
if (!to) return [
|
|
2147
|
+
name,
|
|
2148
|
+
flags
|
|
2149
|
+
];
|
|
2150
|
+
rewritten += 1;
|
|
2151
|
+
return [
|
|
2152
|
+
to,
|
|
2153
|
+
flags
|
|
2154
|
+
];
|
|
2155
|
+
}));
|
|
2156
|
+
}
|
|
2157
|
+
if (rewritten === 0) return {
|
|
2158
|
+
text,
|
|
2159
|
+
rewritten: 0,
|
|
2160
|
+
ambiguous
|
|
2161
|
+
};
|
|
2162
|
+
const indent = /^[ \t]+/m.exec(text)?.[0] ?? " ";
|
|
2163
|
+
const trailing = text.endsWith("\n") ? "\n" : "";
|
|
2164
|
+
return {
|
|
2165
|
+
text: JSON.stringify(manifest, null, indent) + trailing,
|
|
2166
|
+
rewritten,
|
|
2167
|
+
ambiguous
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
__name(rewriteManifest, "rewriteManifest");
|
|
2017
2171
|
|
|
2018
2172
|
// src/init/patch-app-module.ts
|
|
2019
2173
|
var MARKER = "SaaSiCatModule.forRoot";
|
|
@@ -2035,7 +2189,7 @@ ${block}`;
|
|
|
2035
2189
|
return {
|
|
2036
2190
|
source,
|
|
2037
2191
|
status: "declined",
|
|
2038
|
-
reason: "no `@Module({
|
|
2192
|
+
reason: "this file has no `@Module({ ... })` with an `imports:` key on a line of its own \u2014 the shape `nest new` writes \u2014 so there is nowhere to add the platform without guessing at the structure",
|
|
2039
2193
|
manualBlock
|
|
2040
2194
|
};
|
|
2041
2195
|
}
|
|
@@ -2123,6 +2277,13 @@ function renderForRootBlock(options) {
|
|
|
2123
2277
|
" // your whole capability inventory \u2014 and the manifest routes to",
|
|
2124
2278
|
" // anyone who asks. Import your guard and put it in.",
|
|
2125
2279
|
" controller: { guards: [YourAuthGuard] },",
|
|
2280
|
+
" // The modules whose providers the platform injects: the one",
|
|
2281
|
+
" // exporting PrismaService, and the one your guard depends on.",
|
|
2282
|
+
" // `prismaPersistence({ client: PrismaService })` is resolved",
|
|
2283
|
+
" // inside the platform module, which sees only what is listed",
|
|
2284
|
+
" // here or declared @Global. Same rule as the guard: this does",
|
|
2285
|
+
" // not compile until you name them.",
|
|
2286
|
+
" imports: [YourPrismaModule, YourAuthModule],",
|
|
2126
2287
|
options.persistenceImport ? " persistence," : " // persistence: prismaPersistence({ client: PrismaService }),",
|
|
2127
2288
|
" catalog: { featureUiRegistry: " + options.registry.constName + " },",
|
|
2128
2289
|
" adminResources: true,",
|
|
@@ -2769,7 +2930,7 @@ DoctorCommands = _ts_decorate12([
|
|
|
2769
2930
|
|
|
2770
2931
|
// src/discovery.command.ts
|
|
2771
2932
|
var import_node_fs = require("fs");
|
|
2772
|
-
var
|
|
2933
|
+
var import_node_path2 = require("path");
|
|
2773
2934
|
var import_common13 = require("@nestjs/common");
|
|
2774
2935
|
var import_nest_commander5 = require("nest-commander");
|
|
2775
2936
|
var import_nest6 = require("@saasicat/nest");
|
|
@@ -2807,8 +2968,8 @@ var DiscoveryScanCommand = class extends import_nest_commander5.CommandRunner {
|
|
|
2807
2968
|
try {
|
|
2808
2969
|
const snapshot = this.scanner.rebuildSnapshot();
|
|
2809
2970
|
if (flags.out) {
|
|
2810
|
-
const outPath = (0,
|
|
2811
|
-
(0, import_node_fs.mkdirSync)((0,
|
|
2971
|
+
const outPath = (0, import_node_path2.resolve)(flags.out);
|
|
2972
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(outPath), {
|
|
2812
2973
|
recursive: true
|
|
2813
2974
|
});
|
|
2814
2975
|
(0, import_node_fs.writeFileSync)(outPath, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
|
|
@@ -2817,7 +2978,7 @@ var DiscoveryScanCommand = class extends import_nest_commander5.CommandRunner {
|
|
|
2817
2978
|
process.stdout.write(`Discovery-Scan (${snapshot.app.key} v${snapshot.app.version}): ${snapshot.capabilities.length} Capabilities \xB7 ${snapshot.features.length} Features \xB7 ${snapshot.quotas.length} Quotas \xB7 hash ${snapshot.hash.slice(0, 19)}\u2026
|
|
2818
2979
|
`);
|
|
2819
2980
|
if (target) {
|
|
2820
|
-
process.stdout.write(`Snapshot persisted: ${(0,
|
|
2981
|
+
process.stdout.write(`Snapshot persisted: ${(0, import_node_path2.resolve)(target)}
|
|
2821
2982
|
`);
|
|
2822
2983
|
} else {
|
|
2823
2984
|
process.stderr.write("WARNING: the snapshot was not persisted \u2014 neither snapshotPath (DiscoveryModule.forRoot) configured nor --out given. The seed gate will find no snapshot this way.\n");
|
|
@@ -3246,6 +3407,9 @@ UserCommands = _ts_decorate14([
|
|
|
3246
3407
|
enableFkPointers,
|
|
3247
3408
|
extractBlockNames,
|
|
3248
3409
|
extractBlocks,
|
|
3410
|
+
extractEnumBlocks,
|
|
3411
|
+
extractEnumNames,
|
|
3412
|
+
extractFragmentBlocks,
|
|
3249
3413
|
extractModelBlocks,
|
|
3250
3414
|
extractModelNames,
|
|
3251
3415
|
findFkPointers,
|
|
@@ -3254,6 +3418,7 @@ UserCommands = _ts_decorate14([
|
|
|
3254
3418
|
hasConstraints,
|
|
3255
3419
|
isNoLongerPublic,
|
|
3256
3420
|
isOneToOne,
|
|
3421
|
+
judgeModuleResolution,
|
|
3257
3422
|
kebabCase,
|
|
3258
3423
|
migrationCreatedBy,
|
|
3259
3424
|
minimumQuotasPerPlan,
|
|
@@ -3269,9 +3434,11 @@ UserCommands = _ts_decorate14([
|
|
|
3269
3434
|
planInit,
|
|
3270
3435
|
projectKeyPattern,
|
|
3271
3436
|
quotaKeyPattern,
|
|
3437
|
+
readEffectiveModuleResolution,
|
|
3272
3438
|
relationNameOf,
|
|
3273
3439
|
reportConstraints,
|
|
3274
3440
|
rewriteImports,
|
|
3441
|
+
rewriteManifest,
|
|
3275
3442
|
rewriteNames,
|
|
3276
3443
|
rewriteSubpath,
|
|
3277
3444
|
stripLineComment,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MfaService, AdminAuditService, AdminManifestService, DiscoverySnapshot, ProviderSpec, DiscoveryScanner } from '@saasicat/nest';
|
|
2
|
-
import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/
|
|
2
|
+
import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/core';
|
|
3
3
|
import { Type, DynamicModule } from '@nestjs/common';
|
|
4
|
+
import * as TypeScript from 'typescript';
|
|
4
5
|
import { CommandRunner } from 'nest-commander';
|
|
5
6
|
|
|
6
7
|
declare const CLI_CONTEXT_CONFIG_TOKEN: unique symbol;
|
|
@@ -381,19 +382,41 @@ declare function extractModelNames(schema: string): string[];
|
|
|
381
382
|
* `name -> complete block text incl. opening/closing braces`.
|
|
382
383
|
*/
|
|
383
384
|
declare function extractModelBlocks(fragment: string): Map<string, string>;
|
|
385
|
+
/** Returns all `enum X { ... }` blocks from a fragment, keyed by name. */
|
|
386
|
+
declare function extractEnumBlocks(fragment: string): Map<string, string>;
|
|
387
|
+
/** Names of all top-level `enum X { ... }` blocks in the schema. */
|
|
388
|
+
declare function extractEnumNames(schema: string): string[];
|
|
389
|
+
/**
|
|
390
|
+
* What a fragment contributes: its enums and its models. Both travel
|
|
391
|
+
* together, because a model's `BillingCycle` field is a validation error in
|
|
392
|
+
* a schema that has the model and not the enum — which is what `apply` used
|
|
393
|
+
* to produce for every consumer whose schema did not already carry the enums
|
|
394
|
+
* by hand, and what the example app masked by carrying them.
|
|
395
|
+
*/
|
|
396
|
+
interface FragmentBlocks {
|
|
397
|
+
enums: Map<string, string>;
|
|
398
|
+
models: Map<string, string>;
|
|
399
|
+
}
|
|
400
|
+
declare function extractFragmentBlocks(fragment: string): FragmentBlocks;
|
|
384
401
|
interface ApplyResult {
|
|
385
|
-
/**
|
|
402
|
+
/** Models that were added. */
|
|
386
403
|
added: string[];
|
|
387
|
-
/**
|
|
404
|
+
/** Models that were already present (no write). */
|
|
388
405
|
skipped: string[];
|
|
389
|
-
/**
|
|
406
|
+
/** Enums that were added, before the models that use them. */
|
|
407
|
+
addedEnums: string[];
|
|
408
|
+
/** Enums that were already present (no write). */
|
|
409
|
+
skippedEnums: string[];
|
|
410
|
+
/** Resulting schema text. When nothing was added, identical to the input. */
|
|
390
411
|
schema: string;
|
|
391
412
|
}
|
|
392
413
|
/**
|
|
393
|
-
* Appends missing models from `fragmentBlocks` to the end of
|
|
394
|
-
*
|
|
414
|
+
* Appends missing enums and models from `fragmentBlocks` to the end of
|
|
415
|
+
* `schema`. Existing blocks (same name) stay unchanged and are listed as
|
|
416
|
+
* skipped. A plain map of models is accepted for the callers that only ever
|
|
417
|
+
* had models; the enums are then `[]`, which is what they were before.
|
|
395
418
|
*/
|
|
396
|
-
declare function applyFragmentBlocks(schema: string, fragmentBlocks: Map<string, string>, options?: {
|
|
419
|
+
declare function applyFragmentBlocks(schema: string, fragmentBlocks: FragmentBlocks | Map<string, string>, options?: {
|
|
397
420
|
fragmentLabel?: string;
|
|
398
421
|
}): ApplyResult;
|
|
399
422
|
|
|
@@ -804,6 +827,33 @@ interface PatchOptionsFromPlan {
|
|
|
804
827
|
};
|
|
805
828
|
}
|
|
806
829
|
|
|
830
|
+
interface ModuleResolutionVerdict {
|
|
831
|
+
readonly ok: boolean;
|
|
832
|
+
/** The effective setting, lower-cased as TypeScript names it, or null when unset. */
|
|
833
|
+
readonly value: string | null;
|
|
834
|
+
readonly reason?: string;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Judges an effective `moduleResolution`, as `readEffectiveModuleResolution`
|
|
838
|
+
* reports it.
|
|
839
|
+
*
|
|
840
|
+
* Unset is `ok`: TypeScript then derives it from `module`, and a `module`
|
|
841
|
+
* that implies the old resolution is a setup this cannot see from here —
|
|
842
|
+
* the next build says so, with TypeScript's own message.
|
|
843
|
+
*/
|
|
844
|
+
declare function judgeModuleResolution(value: string | null): ModuleResolutionVerdict;
|
|
845
|
+
/**
|
|
846
|
+
* The `moduleResolution` a project's `tsconfig.json` resolves to, after
|
|
847
|
+
* `extends`, lower-cased as TypeScript names the kind (`node10`, `node16`,
|
|
848
|
+
* `nodenext`, `bundler`, `classic`) — or null when there is no config, the
|
|
849
|
+
* config cannot be parsed, or the option is not set anywhere in the chain.
|
|
850
|
+
*
|
|
851
|
+
* `ts` is whichever TypeScript will compile the project: the consumer's own
|
|
852
|
+
* when it can be resolved from the project root, so the reading matches the
|
|
853
|
+
* build that follows.
|
|
854
|
+
*/
|
|
855
|
+
declare function readEffectiveModuleResolution(root: string, ts: typeof TypeScript): string | null;
|
|
856
|
+
|
|
807
857
|
/** The pattern `plan-catalog.schema.json` puts on `projectKey`. */
|
|
808
858
|
declare function projectKeyPattern(): RegExp;
|
|
809
859
|
/** The pattern it puts on the keys inside a plan's `quotas` object. */
|
|
@@ -878,6 +928,13 @@ interface RenameTable {
|
|
|
878
928
|
readonly entryTokens: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
879
929
|
/** A module specifier prefix and its replacement. */
|
|
880
930
|
readonly subpaths: Readonly<Record<string, string>>;
|
|
931
|
+
/**
|
|
932
|
+
* A package that was renamed. Rewritten in specifiers by `rewriteNames`
|
|
933
|
+
* and in `package.json` dependency fields by `rewriteManifest` — both,
|
|
934
|
+
* because an import a manifest does not declare fails to resolve under
|
|
935
|
+
* pnpm's isolated `node_modules`.
|
|
936
|
+
*/
|
|
937
|
+
readonly packages?: Readonly<Record<string, string>>;
|
|
881
938
|
}
|
|
882
939
|
interface RenameResult {
|
|
883
940
|
readonly text: string;
|
|
@@ -903,6 +960,26 @@ declare function namedImports(text: string): Array<{
|
|
|
903
960
|
}>;
|
|
904
961
|
/** Applies the table to one file's text. Idempotent: a second run changes nothing. */
|
|
905
962
|
declare function rewriteNames(text: string, table: RenameTable): RenameResult;
|
|
963
|
+
/**
|
|
964
|
+
* Applies the package renames to a `package.json` text.
|
|
965
|
+
*
|
|
966
|
+
* Parsed and re-serialised rather than string-replaced, so a rename lands in a
|
|
967
|
+
* dependency field and nowhere else — not in `name`, not in a description.
|
|
968
|
+
* The file's indentation is kept; a consumer's formatter must not see a diff
|
|
969
|
+
* it did not cause. Returns the text unchanged when nothing applied.
|
|
970
|
+
*/
|
|
971
|
+
interface ManifestRewriteOptions {
|
|
972
|
+
/**
|
|
973
|
+
* The range the renamed dependency gets — `^<the version this CLI was
|
|
974
|
+
* released as>`. The old range cannot be carried over: a 0.x consumer
|
|
975
|
+
* declares `"@saasicat/types": "^0.27.0"`, and `@saasicat/core` has no
|
|
976
|
+
* 0.27 — the rename starts on the 1.0 line. The caller passes the CLI's
|
|
977
|
+
* own version because that IS the line the consumer is migrating to;
|
|
978
|
+
* the codemod ships with it.
|
|
979
|
+
*/
|
|
980
|
+
readonly targetRange: string;
|
|
981
|
+
}
|
|
982
|
+
declare function rewriteManifest(text: string, table: RenameTable, options: ManifestRewriteOptions): RenameResult;
|
|
906
983
|
|
|
907
984
|
interface PatchAppModuleOptions {
|
|
908
985
|
/** Import specifier for the persistence bundle, or null when not generated. */
|
|
@@ -1147,4 +1224,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
1147
1224
|
parsePassword(val: string): string;
|
|
1148
1225
|
}
|
|
1149
1226
|
|
|
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 };
|
|
1227
|
+
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 FragmentBlocks, type InitOptions, type InitPlan, LIMIT_FILTER_IMPORTS, LIMIT_FILTER_PROVIDER, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, type ManifestRewriteOptions, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, type ModuleResolutionVerdict, type MoveTable, 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, extractEnumBlocks, extractEnumNames, extractFragmentBlocks, extractModelBlocks, extractModelNames, findFkPointers, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, judgeModuleResolution, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, projectKeyPattern, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MfaService, AdminAuditService, AdminManifestService, DiscoverySnapshot, ProviderSpec, DiscoveryScanner } from '@saasicat/nest';
|
|
2
|
-
import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/
|
|
2
|
+
import { UserPort, PlatformUserDto, AuditQueryPort, AuditEntry, AdminManifest, ManifestAccessPort, PlanCatalog, UserManagementPort } from '@saasicat/core';
|
|
3
3
|
import { Type, DynamicModule } from '@nestjs/common';
|
|
4
|
+
import * as TypeScript from 'typescript';
|
|
4
5
|
import { CommandRunner } from 'nest-commander';
|
|
5
6
|
|
|
6
7
|
declare const CLI_CONTEXT_CONFIG_TOKEN: unique symbol;
|
|
@@ -381,19 +382,41 @@ declare function extractModelNames(schema: string): string[];
|
|
|
381
382
|
* `name -> complete block text incl. opening/closing braces`.
|
|
382
383
|
*/
|
|
383
384
|
declare function extractModelBlocks(fragment: string): Map<string, string>;
|
|
385
|
+
/** Returns all `enum X { ... }` blocks from a fragment, keyed by name. */
|
|
386
|
+
declare function extractEnumBlocks(fragment: string): Map<string, string>;
|
|
387
|
+
/** Names of all top-level `enum X { ... }` blocks in the schema. */
|
|
388
|
+
declare function extractEnumNames(schema: string): string[];
|
|
389
|
+
/**
|
|
390
|
+
* What a fragment contributes: its enums and its models. Both travel
|
|
391
|
+
* together, because a model's `BillingCycle` field is a validation error in
|
|
392
|
+
* a schema that has the model and not the enum — which is what `apply` used
|
|
393
|
+
* to produce for every consumer whose schema did not already carry the enums
|
|
394
|
+
* by hand, and what the example app masked by carrying them.
|
|
395
|
+
*/
|
|
396
|
+
interface FragmentBlocks {
|
|
397
|
+
enums: Map<string, string>;
|
|
398
|
+
models: Map<string, string>;
|
|
399
|
+
}
|
|
400
|
+
declare function extractFragmentBlocks(fragment: string): FragmentBlocks;
|
|
384
401
|
interface ApplyResult {
|
|
385
|
-
/**
|
|
402
|
+
/** Models that were added. */
|
|
386
403
|
added: string[];
|
|
387
|
-
/**
|
|
404
|
+
/** Models that were already present (no write). */
|
|
388
405
|
skipped: string[];
|
|
389
|
-
/**
|
|
406
|
+
/** Enums that were added, before the models that use them. */
|
|
407
|
+
addedEnums: string[];
|
|
408
|
+
/** Enums that were already present (no write). */
|
|
409
|
+
skippedEnums: string[];
|
|
410
|
+
/** Resulting schema text. When nothing was added, identical to the input. */
|
|
390
411
|
schema: string;
|
|
391
412
|
}
|
|
392
413
|
/**
|
|
393
|
-
* Appends missing models from `fragmentBlocks` to the end of
|
|
394
|
-
*
|
|
414
|
+
* Appends missing enums and models from `fragmentBlocks` to the end of
|
|
415
|
+
* `schema`. Existing blocks (same name) stay unchanged and are listed as
|
|
416
|
+
* skipped. A plain map of models is accepted for the callers that only ever
|
|
417
|
+
* had models; the enums are then `[]`, which is what they were before.
|
|
395
418
|
*/
|
|
396
|
-
declare function applyFragmentBlocks(schema: string, fragmentBlocks: Map<string, string>, options?: {
|
|
419
|
+
declare function applyFragmentBlocks(schema: string, fragmentBlocks: FragmentBlocks | Map<string, string>, options?: {
|
|
397
420
|
fragmentLabel?: string;
|
|
398
421
|
}): ApplyResult;
|
|
399
422
|
|
|
@@ -804,6 +827,33 @@ interface PatchOptionsFromPlan {
|
|
|
804
827
|
};
|
|
805
828
|
}
|
|
806
829
|
|
|
830
|
+
interface ModuleResolutionVerdict {
|
|
831
|
+
readonly ok: boolean;
|
|
832
|
+
/** The effective setting, lower-cased as TypeScript names it, or null when unset. */
|
|
833
|
+
readonly value: string | null;
|
|
834
|
+
readonly reason?: string;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Judges an effective `moduleResolution`, as `readEffectiveModuleResolution`
|
|
838
|
+
* reports it.
|
|
839
|
+
*
|
|
840
|
+
* Unset is `ok`: TypeScript then derives it from `module`, and a `module`
|
|
841
|
+
* that implies the old resolution is a setup this cannot see from here —
|
|
842
|
+
* the next build says so, with TypeScript's own message.
|
|
843
|
+
*/
|
|
844
|
+
declare function judgeModuleResolution(value: string | null): ModuleResolutionVerdict;
|
|
845
|
+
/**
|
|
846
|
+
* The `moduleResolution` a project's `tsconfig.json` resolves to, after
|
|
847
|
+
* `extends`, lower-cased as TypeScript names the kind (`node10`, `node16`,
|
|
848
|
+
* `nodenext`, `bundler`, `classic`) — or null when there is no config, the
|
|
849
|
+
* config cannot be parsed, or the option is not set anywhere in the chain.
|
|
850
|
+
*
|
|
851
|
+
* `ts` is whichever TypeScript will compile the project: the consumer's own
|
|
852
|
+
* when it can be resolved from the project root, so the reading matches the
|
|
853
|
+
* build that follows.
|
|
854
|
+
*/
|
|
855
|
+
declare function readEffectiveModuleResolution(root: string, ts: typeof TypeScript): string | null;
|
|
856
|
+
|
|
807
857
|
/** The pattern `plan-catalog.schema.json` puts on `projectKey`. */
|
|
808
858
|
declare function projectKeyPattern(): RegExp;
|
|
809
859
|
/** The pattern it puts on the keys inside a plan's `quotas` object. */
|
|
@@ -878,6 +928,13 @@ interface RenameTable {
|
|
|
878
928
|
readonly entryTokens: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
879
929
|
/** A module specifier prefix and its replacement. */
|
|
880
930
|
readonly subpaths: Readonly<Record<string, string>>;
|
|
931
|
+
/**
|
|
932
|
+
* A package that was renamed. Rewritten in specifiers by `rewriteNames`
|
|
933
|
+
* and in `package.json` dependency fields by `rewriteManifest` — both,
|
|
934
|
+
* because an import a manifest does not declare fails to resolve under
|
|
935
|
+
* pnpm's isolated `node_modules`.
|
|
936
|
+
*/
|
|
937
|
+
readonly packages?: Readonly<Record<string, string>>;
|
|
881
938
|
}
|
|
882
939
|
interface RenameResult {
|
|
883
940
|
readonly text: string;
|
|
@@ -903,6 +960,26 @@ declare function namedImports(text: string): Array<{
|
|
|
903
960
|
}>;
|
|
904
961
|
/** Applies the table to one file's text. Idempotent: a second run changes nothing. */
|
|
905
962
|
declare function rewriteNames(text: string, table: RenameTable): RenameResult;
|
|
963
|
+
/**
|
|
964
|
+
* Applies the package renames to a `package.json` text.
|
|
965
|
+
*
|
|
966
|
+
* Parsed and re-serialised rather than string-replaced, so a rename lands in a
|
|
967
|
+
* dependency field and nowhere else — not in `name`, not in a description.
|
|
968
|
+
* The file's indentation is kept; a consumer's formatter must not see a diff
|
|
969
|
+
* it did not cause. Returns the text unchanged when nothing applied.
|
|
970
|
+
*/
|
|
971
|
+
interface ManifestRewriteOptions {
|
|
972
|
+
/**
|
|
973
|
+
* The range the renamed dependency gets — `^<the version this CLI was
|
|
974
|
+
* released as>`. The old range cannot be carried over: a 0.x consumer
|
|
975
|
+
* declares `"@saasicat/types": "^0.27.0"`, and `@saasicat/core` has no
|
|
976
|
+
* 0.27 — the rename starts on the 1.0 line. The caller passes the CLI's
|
|
977
|
+
* own version because that IS the line the consumer is migrating to;
|
|
978
|
+
* the codemod ships with it.
|
|
979
|
+
*/
|
|
980
|
+
readonly targetRange: string;
|
|
981
|
+
}
|
|
982
|
+
declare function rewriteManifest(text: string, table: RenameTable, options: ManifestRewriteOptions): RenameResult;
|
|
906
983
|
|
|
907
984
|
interface PatchAppModuleOptions {
|
|
908
985
|
/** Import specifier for the persistence bundle, or null when not generated. */
|
|
@@ -1147,4 +1224,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
1147
1224
|
parsePassword(val: string): string;
|
|
1148
1225
|
}
|
|
1149
1226
|
|
|
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 };
|
|
1227
|
+
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 FragmentBlocks, type InitOptions, type InitPlan, LIMIT_FILTER_IMPORTS, LIMIT_FILTER_PROVIDER, MANIFEST_ACCESS_PORT_TOKEN, MANIFEST_CHECKS_TOKEN, type ManifestCheck, ManifestCheckCommand, type ManifestCheckReport, type ManifestCheckResult, ManifestCliFlow, ManifestCommands, type ManifestDiff, ManifestDumpCommand, ManifestHashCommand, type ManifestRewriteOptions, ManifestValidateCommand, MfaSetupFlow, type MfaSetupOptions, type MfaSetupResult, type MissingBlockAttribute, type MissingEnumValue, type MissingField, type ModuleResolutionVerdict, type MoveTable, 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, extractEnumBlocks, extractEnumNames, extractFragmentBlocks, extractModelBlocks, extractModelNames, findFkPointers, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, judgeModuleResolution, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, projectKeyPattern, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
|
package/dist/index.js
CHANGED
|
@@ -1129,13 +1129,43 @@ function extractModelBlocks(fragment) {
|
|
|
1129
1129
|
return extractBlocks(fragment, "model");
|
|
1130
1130
|
}
|
|
1131
1131
|
__name(extractModelBlocks, "extractModelBlocks");
|
|
1132
|
+
function extractEnumBlocks(fragment) {
|
|
1133
|
+
return extractBlocks(fragment, "enum");
|
|
1134
|
+
}
|
|
1135
|
+
__name(extractEnumBlocks, "extractEnumBlocks");
|
|
1136
|
+
function extractEnumNames(schema2) {
|
|
1137
|
+
return extractBlockNames(schema2, "enum");
|
|
1138
|
+
}
|
|
1139
|
+
__name(extractEnumNames, "extractEnumNames");
|
|
1140
|
+
function extractFragmentBlocks(fragment) {
|
|
1141
|
+
return {
|
|
1142
|
+
enums: extractEnumBlocks(fragment),
|
|
1143
|
+
models: extractModelBlocks(fragment)
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
__name(extractFragmentBlocks, "extractFragmentBlocks");
|
|
1132
1147
|
function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
|
|
1133
|
-
const
|
|
1148
|
+
const blocks = fragmentBlocks instanceof Map ? {
|
|
1149
|
+
enums: /* @__PURE__ */ new Map(),
|
|
1150
|
+
models: fragmentBlocks
|
|
1151
|
+
} : fragmentBlocks;
|
|
1152
|
+
const existingModels = new Set(extractModelNames(schema2));
|
|
1153
|
+
const existingEnums = new Set(extractEnumNames(schema2));
|
|
1134
1154
|
const added = [];
|
|
1135
1155
|
const skipped = [];
|
|
1156
|
+
const addedEnums = [];
|
|
1157
|
+
const skippedEnums = [];
|
|
1136
1158
|
const additions = [];
|
|
1137
|
-
for (const [name, block] of
|
|
1138
|
-
if (
|
|
1159
|
+
for (const [name, block] of blocks.enums) {
|
|
1160
|
+
if (existingEnums.has(name)) {
|
|
1161
|
+
skippedEnums.push(name);
|
|
1162
|
+
} else {
|
|
1163
|
+
addedEnums.push(name);
|
|
1164
|
+
additions.push(block);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
for (const [name, block] of blocks.models) {
|
|
1168
|
+
if (existingModels.has(name)) {
|
|
1139
1169
|
skipped.push(name);
|
|
1140
1170
|
} else {
|
|
1141
1171
|
added.push(name);
|
|
@@ -1146,6 +1176,8 @@ function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
|
|
|
1146
1176
|
return {
|
|
1147
1177
|
added,
|
|
1148
1178
|
skipped,
|
|
1179
|
+
addedEnums,
|
|
1180
|
+
skippedEnums,
|
|
1149
1181
|
schema: schema2
|
|
1150
1182
|
};
|
|
1151
1183
|
}
|
|
@@ -1162,6 +1194,8 @@ function applyFragmentBlocks(schema2, fragmentBlocks, options = {}) {
|
|
|
1162
1194
|
return {
|
|
1163
1195
|
added,
|
|
1164
1196
|
skipped,
|
|
1197
|
+
addedEnums,
|
|
1198
|
+
skippedEnums,
|
|
1165
1199
|
schema: trimmedSchema + header + additions.join("\n\n") + "\n"
|
|
1166
1200
|
};
|
|
1167
1201
|
}
|
|
@@ -1716,6 +1750,38 @@ function patchOptionsFor(plan) {
|
|
|
1716
1750
|
}
|
|
1717
1751
|
__name(patchOptionsFor, "patchOptionsFor");
|
|
1718
1752
|
|
|
1753
|
+
// src/init/module-resolution.ts
|
|
1754
|
+
import { join } from "path";
|
|
1755
|
+
var RESOLVES_SUBPATHS = /* @__PURE__ */ new Set([
|
|
1756
|
+
"node16",
|
|
1757
|
+
"nodenext",
|
|
1758
|
+
"bundler"
|
|
1759
|
+
]);
|
|
1760
|
+
function judgeModuleResolution(value) {
|
|
1761
|
+
if (value === null || RESOLVES_SUBPATHS.has(value)) {
|
|
1762
|
+
return {
|
|
1763
|
+
ok: true,
|
|
1764
|
+
value
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
return {
|
|
1768
|
+
ok: false,
|
|
1769
|
+
value,
|
|
1770
|
+
reason: `tsconfig.json resolves modules with "moduleResolution": "${value}"` + (value === "node10" ? ' (what TypeScript calls the "node" setting)' : "") + '. The files init writes import subpath exports (`@saasicat/nest/platform`, `/billing`, `/discovery`), which TypeScript resolves only under "node16", "nodenext" or "bundler". Set one of those first \u2014 `nest new` has used "nodenext" since NestJS 10.'
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
__name(judgeModuleResolution, "judgeModuleResolution");
|
|
1774
|
+
function readEffectiveModuleResolution(root, ts) {
|
|
1775
|
+
const configPath = join(root, "tsconfig.json");
|
|
1776
|
+
const read = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
1777
|
+
if (read.error || read.config === void 0) return null;
|
|
1778
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, root);
|
|
1779
|
+
const kind = parsed.options.moduleResolution;
|
|
1780
|
+
if (kind === void 0) return null;
|
|
1781
|
+
return ts.ModuleResolutionKind[kind].toLowerCase();
|
|
1782
|
+
}
|
|
1783
|
+
__name(readEffectiveModuleResolution, "readEffectiveModuleResolution");
|
|
1784
|
+
|
|
1719
1785
|
// src/codemods/v1-imports.ts
|
|
1720
1786
|
var PUBLIC_PREFIXES = [
|
|
1721
1787
|
"ui/",
|
|
@@ -1881,6 +1947,13 @@ function rewriteNames(text, table) {
|
|
|
1881
1947
|
return `${quote}${to}`;
|
|
1882
1948
|
});
|
|
1883
1949
|
}
|
|
1950
|
+
for (const [from, to] of Object.entries(table.packages ?? {})) {
|
|
1951
|
+
if (from === "_") continue;
|
|
1952
|
+
next = next.replace(new RegExp(`${escape(from)}(?=['"\`/])`, "g"), () => {
|
|
1953
|
+
rewritten += 1;
|
|
1954
|
+
return to;
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1884
1957
|
for (const [from, to] of Object.entries(table.subpaths)) {
|
|
1885
1958
|
next = next.replace(new RegExp(escape(from), "g"), () => {
|
|
1886
1959
|
rewritten += 1;
|
|
@@ -1896,6 +1969,81 @@ function rewriteNames(text, table) {
|
|
|
1896
1969
|
};
|
|
1897
1970
|
}
|
|
1898
1971
|
__name(rewriteNames, "rewriteNames");
|
|
1972
|
+
var DEPENDENCY_FIELDS = [
|
|
1973
|
+
"dependencies",
|
|
1974
|
+
"devDependencies",
|
|
1975
|
+
"peerDependencies",
|
|
1976
|
+
"optionalDependencies"
|
|
1977
|
+
];
|
|
1978
|
+
var UNTRANSLATABLE_RANGE = /^(workspace:|file:|link:|npm:|git\+|https?:)/;
|
|
1979
|
+
function rewriteManifest(text, table, options) {
|
|
1980
|
+
const renames = Object.entries(table.packages ?? {}).filter(([from]) => from !== "_");
|
|
1981
|
+
const ambiguous = [];
|
|
1982
|
+
let manifest;
|
|
1983
|
+
try {
|
|
1984
|
+
manifest = JSON.parse(text);
|
|
1985
|
+
} catch {
|
|
1986
|
+
return {
|
|
1987
|
+
text,
|
|
1988
|
+
rewritten: 0,
|
|
1989
|
+
ambiguous
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
let rewritten = 0;
|
|
1993
|
+
for (const field of DEPENDENCY_FIELDS) {
|
|
1994
|
+
const deps = manifest[field];
|
|
1995
|
+
if (!deps || typeof deps !== "object") continue;
|
|
1996
|
+
const entries = Object.entries(deps);
|
|
1997
|
+
const renamed = entries.map(([name, range]) => {
|
|
1998
|
+
const to = renames.find(([from]) => from === name)?.[1];
|
|
1999
|
+
if (!to) return [
|
|
2000
|
+
name,
|
|
2001
|
+
range
|
|
2002
|
+
];
|
|
2003
|
+
if (UNTRANSLATABLE_RANGE.test(range)) {
|
|
2004
|
+
ambiguous.push(`${name} in ${field} (${range})`);
|
|
2005
|
+
return [
|
|
2006
|
+
name,
|
|
2007
|
+
range
|
|
2008
|
+
];
|
|
2009
|
+
}
|
|
2010
|
+
rewritten += 1;
|
|
2011
|
+
return [
|
|
2012
|
+
to,
|
|
2013
|
+
options.targetRange
|
|
2014
|
+
];
|
|
2015
|
+
});
|
|
2016
|
+
manifest[field] = Object.fromEntries(renamed);
|
|
2017
|
+
}
|
|
2018
|
+
const meta = manifest.peerDependenciesMeta;
|
|
2019
|
+
if (meta && typeof meta === "object") {
|
|
2020
|
+
manifest.peerDependenciesMeta = Object.fromEntries(Object.entries(meta).map(([name, flags]) => {
|
|
2021
|
+
const to = renames.find(([from]) => from === name)?.[1];
|
|
2022
|
+
if (!to) return [
|
|
2023
|
+
name,
|
|
2024
|
+
flags
|
|
2025
|
+
];
|
|
2026
|
+
rewritten += 1;
|
|
2027
|
+
return [
|
|
2028
|
+
to,
|
|
2029
|
+
flags
|
|
2030
|
+
];
|
|
2031
|
+
}));
|
|
2032
|
+
}
|
|
2033
|
+
if (rewritten === 0) return {
|
|
2034
|
+
text,
|
|
2035
|
+
rewritten: 0,
|
|
2036
|
+
ambiguous
|
|
2037
|
+
};
|
|
2038
|
+
const indent = /^[ \t]+/m.exec(text)?.[0] ?? " ";
|
|
2039
|
+
const trailing = text.endsWith("\n") ? "\n" : "";
|
|
2040
|
+
return {
|
|
2041
|
+
text: JSON.stringify(manifest, null, indent) + trailing,
|
|
2042
|
+
rewritten,
|
|
2043
|
+
ambiguous
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
__name(rewriteManifest, "rewriteManifest");
|
|
1899
2047
|
|
|
1900
2048
|
// src/init/patch-app-module.ts
|
|
1901
2049
|
var MARKER = "SaaSiCatModule.forRoot";
|
|
@@ -1917,7 +2065,7 @@ ${block}`;
|
|
|
1917
2065
|
return {
|
|
1918
2066
|
source,
|
|
1919
2067
|
status: "declined",
|
|
1920
|
-
reason: "no `@Module({
|
|
2068
|
+
reason: "this file has no `@Module({ ... })` with an `imports:` key on a line of its own \u2014 the shape `nest new` writes \u2014 so there is nowhere to add the platform without guessing at the structure",
|
|
1921
2069
|
manualBlock
|
|
1922
2070
|
};
|
|
1923
2071
|
}
|
|
@@ -2005,6 +2153,13 @@ function renderForRootBlock(options) {
|
|
|
2005
2153
|
" // your whole capability inventory \u2014 and the manifest routes to",
|
|
2006
2154
|
" // anyone who asks. Import your guard and put it in.",
|
|
2007
2155
|
" controller: { guards: [YourAuthGuard] },",
|
|
2156
|
+
" // The modules whose providers the platform injects: the one",
|
|
2157
|
+
" // exporting PrismaService, and the one your guard depends on.",
|
|
2158
|
+
" // `prismaPersistence({ client: PrismaService })` is resolved",
|
|
2159
|
+
" // inside the platform module, which sees only what is listed",
|
|
2160
|
+
" // here or declared @Global. Same rule as the guard: this does",
|
|
2161
|
+
" // not compile until you name them.",
|
|
2162
|
+
" imports: [YourPrismaModule, YourAuthModule],",
|
|
2008
2163
|
options.persistenceImport ? " persistence," : " // persistence: prismaPersistence({ client: PrismaService }),",
|
|
2009
2164
|
" catalog: { featureUiRegistry: " + options.registry.constName + " },",
|
|
2010
2165
|
" adminResources: true,",
|
|
@@ -3127,6 +3282,9 @@ export {
|
|
|
3127
3282
|
enableFkPointers,
|
|
3128
3283
|
extractBlockNames,
|
|
3129
3284
|
extractBlocks,
|
|
3285
|
+
extractEnumBlocks,
|
|
3286
|
+
extractEnumNames,
|
|
3287
|
+
extractFragmentBlocks,
|
|
3130
3288
|
extractModelBlocks,
|
|
3131
3289
|
extractModelNames,
|
|
3132
3290
|
findFkPointers,
|
|
@@ -3135,6 +3293,7 @@ export {
|
|
|
3135
3293
|
hasConstraints,
|
|
3136
3294
|
isNoLongerPublic,
|
|
3137
3295
|
isOneToOne,
|
|
3296
|
+
judgeModuleResolution,
|
|
3138
3297
|
kebabCase,
|
|
3139
3298
|
migrationCreatedBy,
|
|
3140
3299
|
minimumQuotasPerPlan,
|
|
@@ -3150,9 +3309,11 @@ export {
|
|
|
3150
3309
|
planInit,
|
|
3151
3310
|
projectKeyPattern,
|
|
3152
3311
|
quotaKeyPattern,
|
|
3312
|
+
readEffectiveModuleResolution,
|
|
3153
3313
|
relationNameOf,
|
|
3154
3314
|
reportConstraints,
|
|
3155
3315
|
rewriteImports,
|
|
3316
|
+
rewriteManifest,
|
|
3156
3317
|
rewriteNames,
|
|
3157
3318
|
rewriteSubpath,
|
|
3158
3319
|
stripLineComment,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/cli",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
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",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"types": "./dist/index.d.cts",
|
|
17
17
|
"default": "./dist/index.cjs"
|
|
18
18
|
}
|
|
19
|
-
}
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
|
@@ -29,9 +30,9 @@
|
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"qrcode-terminal": "^0.12.0",
|
|
32
|
-
"@saasicat/nest": "^1.0.0-rc.
|
|
33
|
-
"@saasicat/spec": "^1.0.0-rc.
|
|
34
|
-
"@saasicat/
|
|
33
|
+
"@saasicat/nest": "^1.0.0-rc.2",
|
|
34
|
+
"@saasicat/spec": "^1.0.0-rc.2",
|
|
35
|
+
"@saasicat/core": "^1.0.0-rc.2"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"@nestjs/common": "^11.0.0",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"access": "public"
|
|
60
61
|
},
|
|
61
62
|
"scripts": {
|
|
62
|
-
"build": "node ../../scripts/build-and-prune.mjs \"tsup src/index.ts --format esm,cjs --dts --external @saasicat/
|
|
63
|
+
"build": "node ../../scripts/build-and-prune.mjs \"tsup src/index.ts --format esm,cjs --dts --external @saasicat/core --external @saasicat/nest --external @nestjs/common --external nest-commander --external qrcode-terminal\"",
|
|
63
64
|
"pretest": "pnpm run build",
|
|
64
65
|
"test": "node --test 'tests/*.test.js'"
|
|
65
66
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Injectable } from '@nestjs/common';
|
|
2
2
|
import { DefinesQuota } from '@saasicat/nest/discovery';
|
|
3
|
-
import type { QuotaProvider } from '@saasicat/
|
|
3
|
+
import type { QuotaProvider } from '@saasicat/core';
|
|
4
4
|
|
|
5
5
|
import { PrismaService } from '../prisma/prisma.service';
|
|
6
6
|
|