@saasicat/cli 1.0.0-rc.7 → 1.0.0-rc.9
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 +8 -0
- package/bin/saasicat.js +91 -3
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +72 -0
- package/dist/index.d.cts +75 -1
- package/dist/index.d.ts +75 -1
- package/dist/index.js +67 -0
- package/package.json +4 -4
- package/templates/init/config/saas.yaml.tpl +24 -0
package/README.md
CHANGED
|
@@ -46,6 +46,14 @@ import { PrismaUserPortAdapter } from './adapters/prisma-user-port';
|
|
|
46
46
|
app: { name: 'MyApp' },
|
|
47
47
|
currency: 'EUR',
|
|
48
48
|
vatRate: 19,
|
|
49
|
+
// Spelled out here like the three above it. In an application
|
|
50
|
+
// these come from `loadPlanCatalogFromFile('config/saas.yaml')` —
|
|
51
|
+
// the settings are never in the database, so the file is the only
|
|
52
|
+
// place they can come from.
|
|
53
|
+
tenantBilling: {
|
|
54
|
+
cancellationNoticeDays: { monthly: 0, yearly: 0 },
|
|
55
|
+
selfServiceBlockedPlans: { asTarget: [], asSource: [] },
|
|
56
|
+
},
|
|
49
57
|
// The catalogue is read from the database, not from a file — the
|
|
50
58
|
// CLI's `plan-catalog import` puts it there.
|
|
51
59
|
sink: {
|
package/bin/saasicat.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
// codemod v1-imports [--dir=X] [--dry-run]
|
|
22
22
|
// codemod v1-rename [--dir=X] [--dry-run]
|
|
23
23
|
// codemod v1-project-key [--dir=X] [--dry-run]
|
|
24
|
-
// codemod v1
|
|
24
|
+
// codemod v1-moved-settings [--dir=X]
|
|
25
|
+
// codemod v1 [--dir=X] [--dry-run] — all four, in that order
|
|
25
26
|
// [--skip-hasher] [--dry-run] [--dir=.]
|
|
26
27
|
// Writes the platform wiring — config, persistence, manifest
|
|
27
28
|
// contribution, admin module, one provider per quota — and adds
|
|
@@ -61,6 +62,10 @@ import {
|
|
|
61
62
|
patchAppModule,
|
|
62
63
|
patchOptionsFor,
|
|
63
64
|
planInit,
|
|
65
|
+
settingsWrittenTo,
|
|
66
|
+
findMovedSettings,
|
|
67
|
+
SCANNED_FOR_MOVED_SETTINGS,
|
|
68
|
+
WHERE_IT_GOES,
|
|
64
69
|
} from '../dist/index.js';
|
|
65
70
|
|
|
66
71
|
const require_ = createRequire(import.meta.url);
|
|
@@ -736,6 +741,51 @@ async function walkSources(root, visit, extra = null) {
|
|
|
736
741
|
* TypeScript, so removing one would sometimes delete a member of the consumer's
|
|
737
742
|
* own type. The migration guide's table says what each shape becomes.
|
|
738
743
|
*/
|
|
744
|
+
/**
|
|
745
|
+
* Names the module options that moved into `config/saas.yaml`.
|
|
746
|
+
*
|
|
747
|
+
* Read-only on purpose — see `codemods/v1-moved-settings.ts` for why removing
|
|
748
|
+
* them would lose the decision instead of moving it. `--dry-run` is accepted
|
|
749
|
+
* and does nothing, so `codemod v1 --dry-run` behaves the same throughout.
|
|
750
|
+
*/
|
|
751
|
+
async function cmdCodemodV1MovedSettings(args) {
|
|
752
|
+
const root = resolve(args.dir ?? '.');
|
|
753
|
+
const found = [];
|
|
754
|
+
|
|
755
|
+
await walkSources(root, async (full, source) => {
|
|
756
|
+
// Code only. The walk includes Markdown, and a documentation file that
|
|
757
|
+
// mentions a setting is not a file that passes one — see
|
|
758
|
+
// `SCANNED_FOR_MOVED_SETTINGS`.
|
|
759
|
+
if (!SCANNED_FOR_MOVED_SETTINGS.test(full)) return;
|
|
760
|
+
for (const { setting, line } of findMovedSettings(source).occurrences) {
|
|
761
|
+
found.push({ where: `${relative(root, full)}:${line}`, setting });
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
if (found.length === 0) {
|
|
766
|
+
console.log('No module option that moved into config/saas.yaml is still passed.');
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
console.log(`${found.length} occurrence(s) of a setting that moved:`);
|
|
771
|
+
const width = Math.max(...found.map((f) => f.where.length));
|
|
772
|
+
for (const { where, setting } of found) {
|
|
773
|
+
console.log(` ${where.padEnd(width)} ${setting}`);
|
|
774
|
+
}
|
|
775
|
+
console.log('');
|
|
776
|
+
console.log(' Not removed, and that is the point: the value is a term somebody agreed,');
|
|
777
|
+
console.log(' and deleting it here without writing it into the file would leave the');
|
|
778
|
+
console.log(' application running on whatever the file happens to say. Move each one:');
|
|
779
|
+
console.log('');
|
|
780
|
+
for (const setting of new Set(found.map((f) => f.setting))) {
|
|
781
|
+
console.log(` ${setting}`);
|
|
782
|
+
console.log(` ${WHERE_IT_GOES[setting]}`);
|
|
783
|
+
}
|
|
784
|
+
console.log('');
|
|
785
|
+
console.log(' TenantBillingModule.forRoot() refuses to boot while either is still');
|
|
786
|
+
console.log(' passed, so this cannot be half-done quietly.');
|
|
787
|
+
}
|
|
788
|
+
|
|
739
789
|
async function cmdCodemodV1ProjectKey(args) {
|
|
740
790
|
const root = resolve(args.dir ?? '.');
|
|
741
791
|
const dryRun = args['dry-run'] === true;
|
|
@@ -874,6 +924,8 @@ async function cmdInit(args, argv) {
|
|
|
874
924
|
for (const w of writes) console.log(` ${w.path}`);
|
|
875
925
|
}
|
|
876
926
|
|
|
927
|
+
reportSettings(writes, root);
|
|
928
|
+
|
|
877
929
|
await patchAppModuleFile(root, plan, args);
|
|
878
930
|
|
|
879
931
|
console.log('');
|
|
@@ -895,6 +947,33 @@ async function cmdInit(args, argv) {
|
|
|
895
947
|
}
|
|
896
948
|
}
|
|
897
949
|
|
|
950
|
+
/**
|
|
951
|
+
* Says which settings the catalogue now carries, with their values and its path.
|
|
952
|
+
*
|
|
953
|
+
* These are required fields without defaults, and there is no second place they
|
|
954
|
+
* can come from. Somebody who has just run `init` should not have to find that
|
|
955
|
+
* out from a boot failure six weeks later — so the command names the file it
|
|
956
|
+
* put them in while they are still looking at the output.
|
|
957
|
+
*
|
|
958
|
+
* Derived in `init/settings-written.ts`, where it can be tested: this file is
|
|
959
|
+
* not.
|
|
960
|
+
*/
|
|
961
|
+
function reportSettings(writes, root) {
|
|
962
|
+
const catalog = writes.find((w) => w.path === 'config/saas.yaml');
|
|
963
|
+
if (!catalog) return;
|
|
964
|
+
const settings = settingsWrittenTo(catalog.content, catalog.path);
|
|
965
|
+
if (settings.length === 0) return;
|
|
966
|
+
|
|
967
|
+
const width = Math.max(...settings.map((s) => s.key.length));
|
|
968
|
+
console.log('');
|
|
969
|
+
console.log(`Settings written to ${join(root, catalog.path)}:`);
|
|
970
|
+
for (const { key, value } of settings) {
|
|
971
|
+
console.log(` ${key.padEnd(width)} ${value}`);
|
|
972
|
+
}
|
|
973
|
+
console.log(' This file is where they live. Editing it is how they change, and the');
|
|
974
|
+
console.log(' change lands on the next restart — the platform reads it at boot.');
|
|
975
|
+
}
|
|
976
|
+
|
|
898
977
|
/**
|
|
899
978
|
* The TypeScript that will compile the project: the consumer's own, resolved
|
|
900
979
|
* from the project root, so the tsconfig is read the way the build reads it.
|
|
@@ -965,12 +1044,16 @@ async function main() {
|
|
|
965
1044
|
if (cmd === 'codemod' && sub === 'v1-project-key') {
|
|
966
1045
|
return cmdCodemodV1ProjectKey(parseArgs(rest));
|
|
967
1046
|
}
|
|
1047
|
+
if (cmd === 'codemod' && sub === 'v1-moved-settings') {
|
|
1048
|
+
return cmdCodemodV1MovedSettings(parseArgs(rest));
|
|
1049
|
+
}
|
|
968
1050
|
if (cmd === 'codemod' && sub === 'v1') {
|
|
969
1051
|
// Imports first: the rename table keys its per-entry tokens by the
|
|
970
1052
|
// specifier they are imported from, which the import rewrite settles.
|
|
971
1053
|
await cmdCodemodV1Imports(parseArgs(rest));
|
|
972
1054
|
await cmdCodemodV1Rename(parseArgs(rest));
|
|
973
|
-
|
|
1055
|
+
await cmdCodemodV1ProjectKey(parseArgs(rest));
|
|
1056
|
+
return cmdCodemodV1MovedSettings(parseArgs(rest));
|
|
974
1057
|
}
|
|
975
1058
|
if (cmd === 'init') {
|
|
976
1059
|
return cmdInit(parseArgs([sub, ...rest].filter(Boolean)), process.argv.slice(3));
|
|
@@ -1005,12 +1088,17 @@ async function main() {
|
|
|
1005
1088
|
console.log(' init --app-key=myapp --quota=notes:Note --quota=seats:Seat');
|
|
1006
1089
|
console.log('');
|
|
1007
1090
|
console.log(' codemod v1 [--dir=.] [--dry-run]');
|
|
1008
|
-
console.log(' the whole 1.0 migration: imports, names, then
|
|
1091
|
+
console.log(' the whole 1.0 migration: imports, names, projectKey, then the');
|
|
1092
|
+
console.log(' settings that moved into config/saas.yaml');
|
|
1009
1093
|
console.log(' codemod v1-imports [--dir=.] [--dry-run]');
|
|
1010
1094
|
console.log(' rewrite @saasicat/ui-vue imports to the 1.0 export map');
|
|
1011
1095
|
console.log(' codemod v1-rename [--dir=.] [--dry-run]');
|
|
1012
1096
|
console.log(' rewrite the names 1.0 changed: SaasPlatform*, Symbol.for keys,');
|
|
1013
1097
|
console.log(' FEATURE_UI_REGISTRY_TOKEN, @saasicat/ui-vue/testing-e2e/*');
|
|
1098
|
+
console.log(' codemod v1-moved-settings [--dir=.]');
|
|
1099
|
+
console.log(' name the module options that moved into config/saas.yaml.');
|
|
1100
|
+
console.log(' Reports only — the value is a commercial decision, and this');
|
|
1101
|
+
console.log(' would delete it without writing it anywhere.');
|
|
1014
1102
|
console.log('');
|
|
1015
1103
|
console.log('Optional --prisma-schema=PATH (default prisma/schema.prisma).');
|
|
1016
1104
|
console.log('Optional --tenant-model=X --user-model=Y for apply/migrate — enables');
|
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d41946fb8c4579edeeaf5f0199b97a2a41d75f2fcf92a77674e97622ff72f631
|
package/dist/index.cjs
CHANGED
|
@@ -64,11 +64,14 @@ __export(index_exports, {
|
|
|
64
64
|
MfaSetupFlow: () => MfaSetupFlow,
|
|
65
65
|
PLATFORM_DOCTOR_CHECK_PROVIDERS: () => PLATFORM_DOCTOR_CHECK_PROVIDERS,
|
|
66
66
|
PlanCatalogDoctorCheck: () => PlanCatalogDoctorCheck,
|
|
67
|
+
SCANNED_FOR_MOVED_SETTINGS: () => SCANNED_FOR_MOVED_SETTINGS,
|
|
68
|
+
SETTINGS_THAT_MOVED: () => SETTINGS_THAT_MOVED,
|
|
67
69
|
UI_VUE_SPECIFIER: () => UI_VUE_SPECIFIER,
|
|
68
70
|
USER_MANAGEMENT_PORT_TOKEN: () => USER_MANAGEMENT_PORT_TOKEN,
|
|
69
71
|
USER_PORT_TOKEN: () => USER_PORT_TOKEN,
|
|
70
72
|
UserCommands: () => UserCommands,
|
|
71
73
|
UserPortDoctorCheck: () => UserPortDoctorCheck,
|
|
74
|
+
WHERE_IT_GOES: () => WHERE_IT_GOES,
|
|
72
75
|
WhoAmIFlow: () => WhoAmIFlow,
|
|
73
76
|
appKeyPattern: () => appKeyPattern,
|
|
74
77
|
appendConstraints: () => appendConstraints,
|
|
@@ -92,6 +95,7 @@ __export(index_exports, {
|
|
|
92
95
|
extractModelBlocks: () => extractModelBlocks,
|
|
93
96
|
extractModelNames: () => extractModelNames,
|
|
94
97
|
findFkPointers: () => findFkPointers,
|
|
98
|
+
findMovedSettings: () => findMovedSettings,
|
|
95
99
|
foreignKeyOf: () => foreignKeyOf,
|
|
96
100
|
hasBackRelation: () => hasBackRelation,
|
|
97
101
|
hasConstraints: () => hasConstraints,
|
|
@@ -120,6 +124,7 @@ __export(index_exports, {
|
|
|
120
124
|
rewriteManifest: () => rewriteManifest,
|
|
121
125
|
rewriteNames: () => rewriteNames,
|
|
122
126
|
rewriteSubpath: () => rewriteSubpath,
|
|
127
|
+
settingsWrittenTo: () => settingsWrittenTo,
|
|
123
128
|
stripLineComment: () => stripLineComment,
|
|
124
129
|
structuralOnly: () => structuralOnly,
|
|
125
130
|
tablesAddressedBy: () => tablesAddressedBy
|
|
@@ -1908,6 +1913,31 @@ function readEffectiveModuleResolution(root, ts) {
|
|
|
1908
1913
|
}
|
|
1909
1914
|
__name(readEffectiveModuleResolution, "readEffectiveModuleResolution");
|
|
1910
1915
|
|
|
1916
|
+
// src/init/settings-written.ts
|
|
1917
|
+
var import_billing = require("@saasicat/nest/billing");
|
|
1918
|
+
function flatten(prefix, value, into) {
|
|
1919
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1920
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1921
|
+
flatten(`${prefix}.${key}`, child, into);
|
|
1922
|
+
}
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
into.push({
|
|
1926
|
+
key: prefix,
|
|
1927
|
+
value: JSON.stringify(value)
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
__name(flatten, "flatten");
|
|
1931
|
+
function settingsWrittenTo(catalogYaml, source = "config/saas.yaml") {
|
|
1932
|
+
const catalog = (0, import_billing.loadPlanCatalogFromString)(catalogYaml, {
|
|
1933
|
+
source
|
|
1934
|
+
});
|
|
1935
|
+
const settings = [];
|
|
1936
|
+
flatten("tenantBilling", catalog.tenantBilling, settings);
|
|
1937
|
+
return settings;
|
|
1938
|
+
}
|
|
1939
|
+
__name(settingsWrittenTo, "settingsWrittenTo");
|
|
1940
|
+
|
|
1911
1941
|
// src/codemods/v1-imports.ts
|
|
1912
1942
|
var PUBLIC_PREFIXES = [
|
|
1913
1943
|
"ui/",
|
|
@@ -2283,6 +2313,43 @@ function removeFromYaml(text) {
|
|
|
2283
2313
|
}
|
|
2284
2314
|
__name(removeFromYaml, "removeFromYaml");
|
|
2285
2315
|
|
|
2316
|
+
// src/codemods/v1-moved-settings.ts
|
|
2317
|
+
var import_spec2 = require("@saasicat/spec");
|
|
2318
|
+
var SETTINGS_THAT_MOVED = Object.keys(import_spec2.planCatalogSchema.properties?.tenantBilling?.properties ?? (() => {
|
|
2319
|
+
throw new Error("plan-catalog.schema.json declares no tenantBilling properties \u2014 @saasicat/spec and @saasicat/cli are out of step.");
|
|
2320
|
+
})());
|
|
2321
|
+
var isIdentifierChar2 = /* @__PURE__ */ __name((ch) => ch !== void 0 && /[A-Za-z0-9_$]/.test(ch), "isIdentifierChar");
|
|
2322
|
+
function lineAt2(text, index) {
|
|
2323
|
+
let line = 1;
|
|
2324
|
+
for (let at = 0; at < index; at += 1) {
|
|
2325
|
+
if (text[at] === "\n") line += 1;
|
|
2326
|
+
}
|
|
2327
|
+
return line;
|
|
2328
|
+
}
|
|
2329
|
+
__name(lineAt2, "lineAt");
|
|
2330
|
+
var SCANNED_FOR_MOVED_SETTINGS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue)$/;
|
|
2331
|
+
function findMovedSettings(text) {
|
|
2332
|
+
const occurrences = [];
|
|
2333
|
+
for (const setting of SETTINGS_THAT_MOVED) {
|
|
2334
|
+
for (let at = text.indexOf(setting); at >= 0; at = text.indexOf(setting, at + 1)) {
|
|
2335
|
+
if (isIdentifierChar2(text[at - 1])) continue;
|
|
2336
|
+
if (isIdentifierChar2(text[at + setting.length])) continue;
|
|
2337
|
+
occurrences.push({
|
|
2338
|
+
setting,
|
|
2339
|
+
line: lineAt2(text, at)
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
return {
|
|
2344
|
+
occurrences: occurrences.sort((a, b) => a.line - b.line || a.setting.localeCompare(b.setting))
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
__name(findMovedSettings, "findMovedSettings");
|
|
2348
|
+
var WHERE_IT_GOES = {
|
|
2349
|
+
cancellationNoticeDays: "config/saas.yaml \u2192 tenantBilling.cancellationNoticeDays \u2014 both `monthly` and `yearly` are required, so write the number you are running today rather than leaving one out.",
|
|
2350
|
+
selfServiceBlockedPlans: 'config/saas.yaml \u2192 tenantBilling.selfServiceBlockedPlans \u2014 both `asTarget` and `asSource` are required, and `[]` is the way to say "nothing is blocked".'
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2286
2353
|
// src/init/patch-app-module.ts
|
|
2287
2354
|
var MARKER = "SaaSiCatModule.forRoot";
|
|
2288
2355
|
function patchAppModule(source, options) {
|
|
@@ -3500,11 +3567,14 @@ UserCommands = _ts_decorate14([
|
|
|
3500
3567
|
MfaSetupFlow,
|
|
3501
3568
|
PLATFORM_DOCTOR_CHECK_PROVIDERS,
|
|
3502
3569
|
PlanCatalogDoctorCheck,
|
|
3570
|
+
SCANNED_FOR_MOVED_SETTINGS,
|
|
3571
|
+
SETTINGS_THAT_MOVED,
|
|
3503
3572
|
UI_VUE_SPECIFIER,
|
|
3504
3573
|
USER_MANAGEMENT_PORT_TOKEN,
|
|
3505
3574
|
USER_PORT_TOKEN,
|
|
3506
3575
|
UserCommands,
|
|
3507
3576
|
UserPortDoctorCheck,
|
|
3577
|
+
WHERE_IT_GOES,
|
|
3508
3578
|
WhoAmIFlow,
|
|
3509
3579
|
appKeyPattern,
|
|
3510
3580
|
appendConstraints,
|
|
@@ -3528,6 +3598,7 @@ UserCommands = _ts_decorate14([
|
|
|
3528
3598
|
extractModelBlocks,
|
|
3529
3599
|
extractModelNames,
|
|
3530
3600
|
findFkPointers,
|
|
3601
|
+
findMovedSettings,
|
|
3531
3602
|
foreignKeyOf,
|
|
3532
3603
|
hasBackRelation,
|
|
3533
3604
|
hasConstraints,
|
|
@@ -3556,6 +3627,7 @@ UserCommands = _ts_decorate14([
|
|
|
3556
3627
|
rewriteManifest,
|
|
3557
3628
|
rewriteNames,
|
|
3558
3629
|
rewriteSubpath,
|
|
3630
|
+
settingsWrittenTo,
|
|
3559
3631
|
stripLineComment,
|
|
3560
3632
|
structuralOnly,
|
|
3561
3633
|
tablesAddressedBy
|
package/dist/index.d.cts
CHANGED
|
@@ -869,6 +869,21 @@ declare function assertValidAppKey(appKey: string): void;
|
|
|
869
869
|
/** The same, for a quota key. `additionalProperties: false` makes it a hard rule. */
|
|
870
870
|
declare function assertValidQuotaKey(quotaKey: string): void;
|
|
871
871
|
|
|
872
|
+
interface WrittenSetting {
|
|
873
|
+
/** Dotted path as it reads in the file, e.g. `tenantBilling.cancellationNoticeDays.monthly`. */
|
|
874
|
+
key: string;
|
|
875
|
+
/** The value as JSON — `0`, `[]`, `"EUR"` — so an empty list is visible as one. */
|
|
876
|
+
value: string;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* The settings a generated `config/saas.yaml` carries.
|
|
880
|
+
*
|
|
881
|
+
* Loaded with the platform's own loader, not a YAML parse: a document `init`
|
|
882
|
+
* writes that the platform would refuse is a bug worth failing the generation
|
|
883
|
+
* for, rather than one the first boot reports after every file exists.
|
|
884
|
+
*/
|
|
885
|
+
declare function settingsWrittenTo(catalogYaml: string, source?: string): WrittenSetting[];
|
|
886
|
+
|
|
872
887
|
/** One entry of the move table: where a file was, and where it went. */
|
|
873
888
|
interface MoveTable {
|
|
874
889
|
readonly moves: Readonly<Record<string, string>>;
|
|
@@ -1000,6 +1015,65 @@ interface ProjectKeyResult {
|
|
|
1000
1015
|
*/
|
|
1001
1016
|
declare function removeProjectKey(text: string, kind?: 'source' | 'yaml'): ProjectKeyResult;
|
|
1002
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* The settings that belong in `config/saas.yaml#tenantBilling`, read off the
|
|
1020
|
+
* schema that defines them.
|
|
1021
|
+
*
|
|
1022
|
+
* Not a list here, and not a copy of the one in `@saasicat/nest`: both derive
|
|
1023
|
+
* from the same schema, so the day a third setting moves into that block, this
|
|
1024
|
+
* codemod names it and the module refuses it without either being edited.
|
|
1025
|
+
*/
|
|
1026
|
+
declare const SETTINGS_THAT_MOVED: readonly string[];
|
|
1027
|
+
type MovedSetting = string;
|
|
1028
|
+
interface MovedSettingOccurrence {
|
|
1029
|
+
/** Which setting it is, so the report can say where it goes. */
|
|
1030
|
+
readonly setting: MovedSetting;
|
|
1031
|
+
/** 1-based line number. */
|
|
1032
|
+
readonly line: number;
|
|
1033
|
+
}
|
|
1034
|
+
interface MovedSettingsResult {
|
|
1035
|
+
readonly occurrences: readonly MovedSettingOccurrence[];
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Files a moved setting can actually be passed in.
|
|
1039
|
+
*
|
|
1040
|
+
* The codemod walk includes Markdown, and both consumers keep large
|
|
1041
|
+
* documentation folders — an upgrade note that mentions `cancellationNoticeDays`
|
|
1042
|
+
* would land in the report beside the line somebody has to change, and a report
|
|
1043
|
+
* that mixes the two is one nobody reads twice. Prose cannot pass a module
|
|
1044
|
+
* option, so prose is not scanned.
|
|
1045
|
+
*/
|
|
1046
|
+
declare const SCANNED_FOR_MOVED_SETTINGS: RegExp;
|
|
1047
|
+
/**
|
|
1048
|
+
* Every occurrence of a moved setting in one source file.
|
|
1049
|
+
*
|
|
1050
|
+
* Matched on word boundaries alone: a longer name that contains it
|
|
1051
|
+
* (`cancellationNoticeDaysV2`) is not reported, and everything else is —
|
|
1052
|
+
* including a mention in a comment or a string.
|
|
1053
|
+
*
|
|
1054
|
+
* That last part is deliberate, and it is the opposite trade from the one this
|
|
1055
|
+
* comment used to claim. Requiring a colon would read as "only a property",
|
|
1056
|
+
* and it would then miss `{ cancellationNoticeDays }` and
|
|
1057
|
+
* `const { cancellationNoticeDays } = options` — two ordinary ways to pass the
|
|
1058
|
+
* same option. Telling a comment from code needs the grammar, which this does
|
|
1059
|
+
* not have. So it over-reports inside code, where the cost is a glance, rather
|
|
1060
|
+
* than under-reporting, where the cost is somebody not learning that their
|
|
1061
|
+
* value is about to stop being read.
|
|
1062
|
+
*
|
|
1063
|
+
* A property access (`config.cancellationNoticeDays`) is reported too: reading
|
|
1064
|
+
* the value back from module options is the same migration, one step further
|
|
1065
|
+
* along.
|
|
1066
|
+
*/
|
|
1067
|
+
declare function findMovedSettings(text: string): MovedSettingsResult;
|
|
1068
|
+
/**
|
|
1069
|
+
* Where a setting goes, for the report.
|
|
1070
|
+
*
|
|
1071
|
+
* One sentence per setting rather than one for both: they end up in the same
|
|
1072
|
+
* block and mean different things, and "move these two to the file" is the
|
|
1073
|
+
* instruction people follow halfway.
|
|
1074
|
+
*/
|
|
1075
|
+
declare const WHERE_IT_GOES: Record<string, string>;
|
|
1076
|
+
|
|
1003
1077
|
interface PatchAppModuleOptions {
|
|
1004
1078
|
/** Import specifier for the persistence bundle, or null when not generated. */
|
|
1005
1079
|
persistenceImport: string | null;
|
|
@@ -1243,4 +1317,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
1243
1317
|
parsePassword(val: string): string;
|
|
1244
1318
|
}
|
|
1245
1319
|
|
|
1246
|
-
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 ProjectKeyResult, 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, appKeyPattern, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidAppKey, 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, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, removeProjectKey, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
|
|
1320
|
+
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, type MovedSetting, type MovedSettingOccurrence, type MovedSettingsResult, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type ProjectKeyResult, type QuotaProviderFile, type QuotaSpec, type RenameResult, type RenameTable, type RewriteResult, SCANNED_FOR_MOVED_SETTINGS, SETTINGS_THAT_MOVED, type SchemaCheckReport, UI_VUE_SPECIFIER, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WHERE_IT_GOES, WhoAmIFlow, type WhoAmIResult, type WrittenSetting, appKeyPattern, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidAppKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, buildImportMap, checkSchema, constraintsFor, enableFkPointers, extractBlockNames, extractBlocks, extractEnumBlocks, extractEnumNames, extractFragmentBlocks, extractModelBlocks, extractModelNames, findFkPointers, findMovedSettings, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, judgeModuleResolution, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, removeProjectKey, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, settingsWrittenTo, stripLineComment, structuralOnly, tablesAddressedBy };
|
package/dist/index.d.ts
CHANGED
|
@@ -869,6 +869,21 @@ declare function assertValidAppKey(appKey: string): void;
|
|
|
869
869
|
/** The same, for a quota key. `additionalProperties: false` makes it a hard rule. */
|
|
870
870
|
declare function assertValidQuotaKey(quotaKey: string): void;
|
|
871
871
|
|
|
872
|
+
interface WrittenSetting {
|
|
873
|
+
/** Dotted path as it reads in the file, e.g. `tenantBilling.cancellationNoticeDays.monthly`. */
|
|
874
|
+
key: string;
|
|
875
|
+
/** The value as JSON — `0`, `[]`, `"EUR"` — so an empty list is visible as one. */
|
|
876
|
+
value: string;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* The settings a generated `config/saas.yaml` carries.
|
|
880
|
+
*
|
|
881
|
+
* Loaded with the platform's own loader, not a YAML parse: a document `init`
|
|
882
|
+
* writes that the platform would refuse is a bug worth failing the generation
|
|
883
|
+
* for, rather than one the first boot reports after every file exists.
|
|
884
|
+
*/
|
|
885
|
+
declare function settingsWrittenTo(catalogYaml: string, source?: string): WrittenSetting[];
|
|
886
|
+
|
|
872
887
|
/** One entry of the move table: where a file was, and where it went. */
|
|
873
888
|
interface MoveTable {
|
|
874
889
|
readonly moves: Readonly<Record<string, string>>;
|
|
@@ -1000,6 +1015,65 @@ interface ProjectKeyResult {
|
|
|
1000
1015
|
*/
|
|
1001
1016
|
declare function removeProjectKey(text: string, kind?: 'source' | 'yaml'): ProjectKeyResult;
|
|
1002
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* The settings that belong in `config/saas.yaml#tenantBilling`, read off the
|
|
1020
|
+
* schema that defines them.
|
|
1021
|
+
*
|
|
1022
|
+
* Not a list here, and not a copy of the one in `@saasicat/nest`: both derive
|
|
1023
|
+
* from the same schema, so the day a third setting moves into that block, this
|
|
1024
|
+
* codemod names it and the module refuses it without either being edited.
|
|
1025
|
+
*/
|
|
1026
|
+
declare const SETTINGS_THAT_MOVED: readonly string[];
|
|
1027
|
+
type MovedSetting = string;
|
|
1028
|
+
interface MovedSettingOccurrence {
|
|
1029
|
+
/** Which setting it is, so the report can say where it goes. */
|
|
1030
|
+
readonly setting: MovedSetting;
|
|
1031
|
+
/** 1-based line number. */
|
|
1032
|
+
readonly line: number;
|
|
1033
|
+
}
|
|
1034
|
+
interface MovedSettingsResult {
|
|
1035
|
+
readonly occurrences: readonly MovedSettingOccurrence[];
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Files a moved setting can actually be passed in.
|
|
1039
|
+
*
|
|
1040
|
+
* The codemod walk includes Markdown, and both consumers keep large
|
|
1041
|
+
* documentation folders — an upgrade note that mentions `cancellationNoticeDays`
|
|
1042
|
+
* would land in the report beside the line somebody has to change, and a report
|
|
1043
|
+
* that mixes the two is one nobody reads twice. Prose cannot pass a module
|
|
1044
|
+
* option, so prose is not scanned.
|
|
1045
|
+
*/
|
|
1046
|
+
declare const SCANNED_FOR_MOVED_SETTINGS: RegExp;
|
|
1047
|
+
/**
|
|
1048
|
+
* Every occurrence of a moved setting in one source file.
|
|
1049
|
+
*
|
|
1050
|
+
* Matched on word boundaries alone: a longer name that contains it
|
|
1051
|
+
* (`cancellationNoticeDaysV2`) is not reported, and everything else is —
|
|
1052
|
+
* including a mention in a comment or a string.
|
|
1053
|
+
*
|
|
1054
|
+
* That last part is deliberate, and it is the opposite trade from the one this
|
|
1055
|
+
* comment used to claim. Requiring a colon would read as "only a property",
|
|
1056
|
+
* and it would then miss `{ cancellationNoticeDays }` and
|
|
1057
|
+
* `const { cancellationNoticeDays } = options` — two ordinary ways to pass the
|
|
1058
|
+
* same option. Telling a comment from code needs the grammar, which this does
|
|
1059
|
+
* not have. So it over-reports inside code, where the cost is a glance, rather
|
|
1060
|
+
* than under-reporting, where the cost is somebody not learning that their
|
|
1061
|
+
* value is about to stop being read.
|
|
1062
|
+
*
|
|
1063
|
+
* A property access (`config.cancellationNoticeDays`) is reported too: reading
|
|
1064
|
+
* the value back from module options is the same migration, one step further
|
|
1065
|
+
* along.
|
|
1066
|
+
*/
|
|
1067
|
+
declare function findMovedSettings(text: string): MovedSettingsResult;
|
|
1068
|
+
/**
|
|
1069
|
+
* Where a setting goes, for the report.
|
|
1070
|
+
*
|
|
1071
|
+
* One sentence per setting rather than one for both: they end up in the same
|
|
1072
|
+
* block and mean different things, and "move these two to the file" is the
|
|
1073
|
+
* instruction people follow halfway.
|
|
1074
|
+
*/
|
|
1075
|
+
declare const WHERE_IT_GOES: Record<string, string>;
|
|
1076
|
+
|
|
1003
1077
|
interface PatchAppModuleOptions {
|
|
1004
1078
|
/** Import specifier for the persistence bundle, or null when not generated. */
|
|
1005
1079
|
persistenceImport: string | null;
|
|
@@ -1243,4 +1317,4 @@ declare class UserCommands extends CommandRunner {
|
|
|
1243
1317
|
parsePassword(val: string): string;
|
|
1244
1318
|
}
|
|
1245
1319
|
|
|
1246
|
-
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 ProjectKeyResult, 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, appKeyPattern, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidAppKey, 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, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, removeProjectKey, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, stripLineComment, structuralOnly, tablesAddressedBy };
|
|
1320
|
+
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, type MovedSetting, type MovedSettingOccurrence, type MovedSettingsResult, PLATFORM_DOCTOR_CHECK_PROVIDERS, type ParsedSchema, type PatchAppModuleOptions, type PatchOptionsFromPlan, type PatchResult, PlanCatalogDoctorCheck, type PlannedFile, type ProjectKeyResult, type QuotaProviderFile, type QuotaSpec, type RenameResult, type RenameTable, type RewriteResult, SCANNED_FOR_MOVED_SETTINGS, SETTINGS_THAT_MOVED, type SchemaCheckReport, UI_VUE_SPECIFIER, USER_MANAGEMENT_PORT_TOKEN, USER_PORT_TOKEN, UserCommands, UserPortDoctorCheck, WHERE_IT_GOES, WhoAmIFlow, type WhoAmIResult, type WrittenSetting, appKeyPattern, appendConstraints, applyFragmentBlocks, applyTokens, assertModelsExist, assertValidAppKey, assertValidQuotaKey, blankStringLiterals, blockBodyLines, breaksContract, buildImportMap, checkSchema, constraintsFor, enableFkPointers, extractBlockNames, extractBlocks, extractEnumBlocks, extractEnumNames, extractFragmentBlocks, extractModelBlocks, extractModelNames, findFkPointers, findMovedSettings, foreignKeyOf, hasBackRelation, hasConstraints, isNoLongerPublic, isOneToOne, judgeModuleResolution, kebabCase, migrationCreatedBy, minimumQuotasPerPlan, namedImports, parseBlockAttributes, parseEnumValues, parseFields, parseQuota, parseSchema, pascalCase, patchAppModule, patchOptionsFor, planInit, quotaKeyPattern, readEffectiveModuleResolution, relationNameOf, removeProjectKey, reportConstraints, rewriteImports, rewriteManifest, rewriteNames, rewriteSubpath, settingsWrittenTo, stripLineComment, structuralOnly, tablesAddressedBy };
|
package/dist/index.js
CHANGED
|
@@ -1783,6 +1783,31 @@ function readEffectiveModuleResolution(root, ts) {
|
|
|
1783
1783
|
}
|
|
1784
1784
|
__name(readEffectiveModuleResolution, "readEffectiveModuleResolution");
|
|
1785
1785
|
|
|
1786
|
+
// src/init/settings-written.ts
|
|
1787
|
+
import { loadPlanCatalogFromString } from "@saasicat/nest/billing";
|
|
1788
|
+
function flatten(prefix, value, into) {
|
|
1789
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1790
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1791
|
+
flatten(`${prefix}.${key}`, child, into);
|
|
1792
|
+
}
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
into.push({
|
|
1796
|
+
key: prefix,
|
|
1797
|
+
value: JSON.stringify(value)
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
__name(flatten, "flatten");
|
|
1801
|
+
function settingsWrittenTo(catalogYaml, source = "config/saas.yaml") {
|
|
1802
|
+
const catalog = loadPlanCatalogFromString(catalogYaml, {
|
|
1803
|
+
source
|
|
1804
|
+
});
|
|
1805
|
+
const settings = [];
|
|
1806
|
+
flatten("tenantBilling", catalog.tenantBilling, settings);
|
|
1807
|
+
return settings;
|
|
1808
|
+
}
|
|
1809
|
+
__name(settingsWrittenTo, "settingsWrittenTo");
|
|
1810
|
+
|
|
1786
1811
|
// src/codemods/v1-imports.ts
|
|
1787
1812
|
var PUBLIC_PREFIXES = [
|
|
1788
1813
|
"ui/",
|
|
@@ -2158,6 +2183,43 @@ function removeFromYaml(text) {
|
|
|
2158
2183
|
}
|
|
2159
2184
|
__name(removeFromYaml, "removeFromYaml");
|
|
2160
2185
|
|
|
2186
|
+
// src/codemods/v1-moved-settings.ts
|
|
2187
|
+
import { planCatalogSchema as planCatalogSchema2 } from "@saasicat/spec";
|
|
2188
|
+
var SETTINGS_THAT_MOVED = Object.keys(planCatalogSchema2.properties?.tenantBilling?.properties ?? (() => {
|
|
2189
|
+
throw new Error("plan-catalog.schema.json declares no tenantBilling properties \u2014 @saasicat/spec and @saasicat/cli are out of step.");
|
|
2190
|
+
})());
|
|
2191
|
+
var isIdentifierChar2 = /* @__PURE__ */ __name((ch) => ch !== void 0 && /[A-Za-z0-9_$]/.test(ch), "isIdentifierChar");
|
|
2192
|
+
function lineAt2(text, index) {
|
|
2193
|
+
let line = 1;
|
|
2194
|
+
for (let at = 0; at < index; at += 1) {
|
|
2195
|
+
if (text[at] === "\n") line += 1;
|
|
2196
|
+
}
|
|
2197
|
+
return line;
|
|
2198
|
+
}
|
|
2199
|
+
__name(lineAt2, "lineAt");
|
|
2200
|
+
var SCANNED_FOR_MOVED_SETTINGS = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue)$/;
|
|
2201
|
+
function findMovedSettings(text) {
|
|
2202
|
+
const occurrences = [];
|
|
2203
|
+
for (const setting of SETTINGS_THAT_MOVED) {
|
|
2204
|
+
for (let at = text.indexOf(setting); at >= 0; at = text.indexOf(setting, at + 1)) {
|
|
2205
|
+
if (isIdentifierChar2(text[at - 1])) continue;
|
|
2206
|
+
if (isIdentifierChar2(text[at + setting.length])) continue;
|
|
2207
|
+
occurrences.push({
|
|
2208
|
+
setting,
|
|
2209
|
+
line: lineAt2(text, at)
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
occurrences: occurrences.sort((a, b) => a.line - b.line || a.setting.localeCompare(b.setting))
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
__name(findMovedSettings, "findMovedSettings");
|
|
2218
|
+
var WHERE_IT_GOES = {
|
|
2219
|
+
cancellationNoticeDays: "config/saas.yaml \u2192 tenantBilling.cancellationNoticeDays \u2014 both `monthly` and `yearly` are required, so write the number you are running today rather than leaving one out.",
|
|
2220
|
+
selfServiceBlockedPlans: 'config/saas.yaml \u2192 tenantBilling.selfServiceBlockedPlans \u2014 both `asTarget` and `asSource` are required, and `[]` is the way to say "nothing is blocked".'
|
|
2221
|
+
};
|
|
2222
|
+
|
|
2161
2223
|
// src/init/patch-app-module.ts
|
|
2162
2224
|
var MARKER = "SaaSiCatModule.forRoot";
|
|
2163
2225
|
function patchAppModule(source, options) {
|
|
@@ -3374,11 +3436,14 @@ export {
|
|
|
3374
3436
|
MfaSetupFlow,
|
|
3375
3437
|
PLATFORM_DOCTOR_CHECK_PROVIDERS,
|
|
3376
3438
|
PlanCatalogDoctorCheck,
|
|
3439
|
+
SCANNED_FOR_MOVED_SETTINGS,
|
|
3440
|
+
SETTINGS_THAT_MOVED,
|
|
3377
3441
|
UI_VUE_SPECIFIER,
|
|
3378
3442
|
USER_MANAGEMENT_PORT_TOKEN,
|
|
3379
3443
|
USER_PORT_TOKEN,
|
|
3380
3444
|
UserCommands,
|
|
3381
3445
|
UserPortDoctorCheck,
|
|
3446
|
+
WHERE_IT_GOES,
|
|
3382
3447
|
WhoAmIFlow,
|
|
3383
3448
|
appKeyPattern,
|
|
3384
3449
|
appendConstraints,
|
|
@@ -3402,6 +3467,7 @@ export {
|
|
|
3402
3467
|
extractModelBlocks,
|
|
3403
3468
|
extractModelNames,
|
|
3404
3469
|
findFkPointers,
|
|
3470
|
+
findMovedSettings,
|
|
3405
3471
|
foreignKeyOf,
|
|
3406
3472
|
hasBackRelation,
|
|
3407
3473
|
hasConstraints,
|
|
@@ -3430,6 +3496,7 @@ export {
|
|
|
3430
3496
|
rewriteManifest,
|
|
3431
3497
|
rewriteNames,
|
|
3432
3498
|
rewriteSubpath,
|
|
3499
|
+
settingsWrittenTo,
|
|
3433
3500
|
stripLineComment,
|
|
3434
3501
|
structuralOnly,
|
|
3435
3502
|
tablesAddressedBy
|
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.9",
|
|
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",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"qrcode-terminal": "^0.12.0",
|
|
33
|
-
"@saasicat/nest": "^1.0.0-rc.
|
|
34
|
-
"@saasicat/spec": "^1.0.0-rc.
|
|
35
|
-
"@saasicat/core": "^1.0.0-rc.
|
|
33
|
+
"@saasicat/nest": "^1.0.0-rc.9",
|
|
34
|
+
"@saasicat/spec": "^1.0.0-rc.9",
|
|
35
|
+
"@saasicat/core": "^1.0.0-rc.9"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@nestjs/common": "^11.0.0",
|
|
@@ -7,6 +7,30 @@ app:
|
|
|
7
7
|
currency: EUR
|
|
8
8
|
vatRate: 19.0
|
|
9
9
|
|
|
10
|
+
# What the tenant self-service routes may do, and on what terms. These live
|
|
11
|
+
# here and nowhere else — an operator reading this file has to be reading the
|
|
12
|
+
# values that are running, with no "unless somebody passed it in code"
|
|
13
|
+
# attached. The file is read at boot, so an edit lands on the next restart.
|
|
14
|
+
tenantBilling:
|
|
15
|
+
# How many days before the term ends a cancellation is still in time.
|
|
16
|
+
# Zero means there is no door to be shut out of: a cancellation on the last
|
|
17
|
+
# day still lands at the term end. Raise it and the cut is hard — a
|
|
18
|
+
# declaration made after the window lands one full period later, which on a
|
|
19
|
+
# yearly cycle is a year.
|
|
20
|
+
#
|
|
21
|
+
# Two rhythms, two numbers, and both are required. A month and a year
|
|
22
|
+
# cannot share a notice period, and a rhythm left out would read as zero:
|
|
23
|
+
# a commercial decision nobody made.
|
|
24
|
+
cancellationNoticeDays:
|
|
25
|
+
monthly: 0
|
|
26
|
+
yearly: 0
|
|
27
|
+
# Plans a tenant may not reach or leave on its own — typically a plan that
|
|
28
|
+
# only a special contract activates. Empty is a statement, not an omission:
|
|
29
|
+
# it says self-service reaches every plan.
|
|
30
|
+
selfServiceBlockedPlans:
|
|
31
|
+
asTarget: []
|
|
32
|
+
asSource: []
|
|
33
|
+
|
|
10
34
|
marketing:
|
|
11
35
|
availableLocales: [en]
|
|
12
36
|
|