@mettlecast/domain-cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +133 -0
- package/dist/builder/build-registry.d.ts +20 -0
- package/dist/builder/build-registry.js +178 -0
- package/dist/builder/build-types.d.ts +10 -0
- package/dist/builder/build-types.js +32 -0
- package/dist/builder/load-domain-module-config.d.ts +12 -0
- package/dist/builder/load-domain-module-config.js +34 -0
- package/dist/builder/load-module.d.ts +26 -0
- package/dist/builder/load-module.js +114 -0
- package/dist/builder/zod-to-json.d.ts +10 -0
- package/dist/builder/zod-to-json.js +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +338 -0
- package/dist/commands/add-api.d.ts +32 -0
- package/dist/commands/add-api.js +106 -0
- package/dist/commands/add-component.d.ts +15 -0
- package/dist/commands/add-component.js +78 -0
- package/dist/commands/add-domain.d.ts +21 -0
- package/dist/commands/add-domain.js +71 -0
- package/dist/commands/add-flow.d.ts +14 -0
- package/dist/commands/add-flow.js +108 -0
- package/dist/commands/add-hook.d.ts +15 -0
- package/dist/commands/add-hook.js +65 -0
- package/dist/commands/add-migration.d.ts +26 -0
- package/dist/commands/add-migration.js +93 -0
- package/dist/commands/add-module.d.ts +7 -0
- package/dist/commands/add-module.js +261 -0
- package/dist/commands/add-page.d.ts +13 -0
- package/dist/commands/add-page.js +170 -0
- package/dist/commands/add-seed-page.d.ts +5 -0
- package/dist/commands/add-seed-page.js +251 -0
- package/dist/commands/add-subscriber.d.ts +30 -0
- package/dist/commands/add-subscriber.js +87 -0
- package/dist/commands/add-test.d.ts +22 -0
- package/dist/commands/add-test.js +285 -0
- package/dist/commands/audit.d.ts +39 -0
- package/dist/commands/audit.js +171 -0
- package/dist/commands/build-catalog.d.ts +68 -0
- package/dist/commands/build-catalog.js +105 -0
- package/dist/commands/build-flows.d.ts +26 -0
- package/dist/commands/build-flows.js +174 -0
- package/dist/commands/build.d.ts +17 -0
- package/dist/commands/build.js +39 -0
- package/dist/commands/check-hashes.d.ts +38 -0
- package/dist/commands/check-hashes.js +97 -0
- package/dist/commands/create-project.d.ts +5 -0
- package/dist/commands/create-project.js +272 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.js +27 -0
- package/dist/commands/doctor.d.ts +48 -0
- package/dist/commands/doctor.js +1301 -0
- package/dist/commands/explain.d.ts +34 -0
- package/dist/commands/explain.js +167 -0
- package/dist/commands/power-tune.d.ts +1 -0
- package/dist/commands/power-tune.js +87 -0
- package/dist/commands/reseed-page.d.ts +6 -0
- package/dist/commands/reseed-page.js +297 -0
- package/dist/commands/show-dns.d.ts +15 -0
- package/dist/commands/show-dns.js +101 -0
- package/dist/commands/show.d.ts +46 -0
- package/dist/commands/show.js +135 -0
- package/dist/commands/test.d.ts +18 -0
- package/dist/commands/test.js +56 -0
- package/dist/commands/upgrade-backend.d.ts +9 -0
- package/dist/commands/upgrade-backend.js +46 -0
- package/dist/commands/upgrade-event.d.ts +22 -0
- package/dist/commands/upgrade-event.js +111 -0
- package/dist/commands/upgrade-frontend.d.ts +5 -0
- package/dist/commands/upgrade-frontend.js +99 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +495 -0
- package/dist/commands/validate.d.ts +30 -0
- package/dist/commands/validate.js +148 -0
- package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
- package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
- package/dist/runtime-stubs/load-secrets.d.ts +11 -0
- package/dist/runtime-stubs/load-secrets.js +37 -0
- package/dist/server/api-server.d.ts +21 -0
- package/dist/server/api-server.js +14 -0
- package/dist/server/mount-routes.d.ts +19 -0
- package/dist/server/mount-routes.js +42 -0
- package/dist/templates/api-skeleton.d.ts +18 -0
- package/dist/templates/api-skeleton.js +69 -0
- package/dist/templates/claude-md.d.ts +4 -0
- package/dist/templates/claude-md.js +7 -0
- package/dist/templates/dashboard-pages/index.d.ts +101 -0
- package/dist/templates/dashboard-pages/index.js +22 -0
- package/dist/templates/domain-config.d.ts +10 -0
- package/dist/templates/domain-config.js +19 -0
- package/dist/templates/events-skeleton.d.ts +9 -0
- package/dist/templates/events-skeleton.js +23 -0
- package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
- package/dist/templates/patterns/api/create-with-event.js +69 -0
- package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
- package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
- package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
- package/dist/templates/patterns/api/paginated-list.js +71 -0
- package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
- package/dist/templates/patterns/api/simple-crud.js +95 -0
- package/dist/templates/patterns/api/system-admin.d.ts +5 -0
- package/dist/templates/patterns/api/system-admin.js +98 -0
- package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
- package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
- package/dist/templates/patterns/component/index.d.ts +25 -0
- package/dist/templates/patterns/component/index.js +814 -0
- package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
- package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
- package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
- package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
- package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
- package/dist/templates/patterns/flow/human-in-loop.js +90 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
- package/dist/templates/patterns/hook/index.d.ts +7 -0
- package/dist/templates/patterns/hook/index.js +378 -0
- package/dist/templates/patterns/migration/column-add.d.ts +5 -0
- package/dist/templates/patterns/migration/column-add.js +52 -0
- package/dist/templates/patterns/migration/event-store.d.ts +6 -0
- package/dist/templates/patterns/migration/event-store.js +97 -0
- package/dist/templates/patterns/migration/index.d.ts +5 -0
- package/dist/templates/patterns/migration/index.js +52 -0
- package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
- package/dist/templates/patterns/migration/rls-policy.js +78 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
- package/dist/templates/patterns/page/dashboard.d.ts +4 -0
- package/dist/templates/patterns/page/dashboard.js +164 -0
- package/dist/templates/patterns/page/detail.d.ts +4 -0
- package/dist/templates/patterns/page/detail.js +162 -0
- package/dist/templates/patterns/page/form.d.ts +4 -0
- package/dist/templates/patterns/page/form.js +192 -0
- package/dist/templates/patterns/page/index.d.ts +8 -0
- package/dist/templates/patterns/page/index.js +14 -0
- package/dist/templates/patterns/page/list.d.ts +4 -0
- package/dist/templates/patterns/page/list.js +138 -0
- package/dist/templates/patterns/page/settings.d.ts +4 -0
- package/dist/templates/patterns/page/settings.js +318 -0
- package/dist/templates/patterns/page/wizard.d.ts +4 -0
- package/dist/templates/patterns/page/wizard.js +293 -0
- package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
- package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
- package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
- package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
- package/dist/templates/readme.d.ts +4 -0
- package/dist/templates/readme.js +7 -0
- package/dist/templates/subscriber-skeleton.d.ts +11 -0
- package/dist/templates/subscriber-skeleton.js +23 -0
- package/dist/utils/checksum.d.ts +18 -0
- package/dist/utils/checksum.js +39 -0
- package/dist/utils/file-helpers.d.ts +35 -0
- package/dist/utils/file-helpers.js +59 -0
- package/dist/utils/header-inject.d.ts +15 -0
- package/dist/utils/header-inject.js +93 -0
- package/dist/utils/install-file.d.ts +17 -0
- package/dist/utils/install-file.js +78 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/manifest.d.ts +28 -0
- package/dist/utils/manifest.js +93 -0
- package/dist/utils/s3-fetch.d.ts +25 -0
- package/dist/utils/s3-fetch.js +52 -0
- package/dist/utils/scaffold-config.d.ts +95 -0
- package/dist/utils/scaffold-config.js +53 -0
- package/dist/utils/token-substitution.d.ts +35 -0
- package/dist/utils/token-substitution.js +52 -0
- package/dist/utils/zod-extractor.d.ts +10 -0
- package/dist/utils/zod-extractor.js +28 -0
- package/package.json +43 -0
- package/src/__tests__/build-registry.test.ts +115 -0
- package/src/__tests__/build-types.test.ts +100 -0
- package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
- package/src/__tests__/commands/add-domain.test.ts +174 -0
- package/src/__tests__/commands/add-flow.test.ts +69 -0
- package/src/__tests__/commands/add-module.test.ts +207 -0
- package/src/__tests__/commands/build-flows.test.ts +154 -0
- package/src/__tests__/commands/check-hashes.test.ts +142 -0
- package/src/__tests__/commands/create-project.test.ts +712 -0
- package/src/__tests__/commands/upgrade.test.ts +762 -0
- package/src/__tests__/doctor.test.ts +291 -0
- package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
- package/src/__tests__/load-domain-module-config.test.ts +10 -0
- package/src/__tests__/mount-routes.test.ts +100 -0
- package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
- package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
- package/src/__tests__/utils/checksum.test.ts +90 -0
- package/src/__tests__/utils/header-inject.test.ts +89 -0
- package/src/__tests__/utils/install-file.test.ts +207 -0
- package/src/__tests__/utils/manifest.test.ts +229 -0
- package/src/__tests__/utils/token-substitution.test.ts +134 -0
- package/src/__tests__/validate.test.ts +18 -0
- package/src/builder/build-registry.ts +237 -0
- package/src/builder/build-types.ts +36 -0
- package/src/builder/load-domain-module-config.ts +43 -0
- package/src/builder/load-module.ts +133 -0
- package/src/builder/zod-to-json.ts +16 -0
- package/src/cli.ts +387 -0
- package/src/commands/add-api.ts +145 -0
- package/src/commands/add-component.ts +116 -0
- package/src/commands/add-domain.ts +100 -0
- package/src/commands/add-flow.ts +136 -0
- package/src/commands/add-hook.ts +100 -0
- package/src/commands/add-migration.ts +129 -0
- package/src/commands/add-module.ts +347 -0
- package/src/commands/add-page.ts +242 -0
- package/src/commands/add-seed-page.ts +338 -0
- package/src/commands/add-subscriber.ts +123 -0
- package/src/commands/add-test.ts +328 -0
- package/src/commands/audit.ts +217 -0
- package/src/commands/build-catalog.ts +181 -0
- package/src/commands/build-flows.ts +198 -0
- package/src/commands/build.ts +59 -0
- package/src/commands/check-hashes.ts +150 -0
- package/src/commands/create-project.ts +367 -0
- package/src/commands/dev.ts +47 -0
- package/src/commands/doctor.ts +1362 -0
- package/src/commands/explain.ts +209 -0
- package/src/commands/power-tune.ts +112 -0
- package/src/commands/reseed-page.ts +410 -0
- package/src/commands/show-dns.ts +139 -0
- package/src/commands/show.ts +179 -0
- package/src/commands/test.ts +91 -0
- package/src/commands/upgrade-backend.ts +72 -0
- package/src/commands/upgrade-event.ts +175 -0
- package/src/commands/upgrade-frontend.ts +135 -0
- package/src/commands/upgrade.ts +668 -0
- package/src/commands/validate.ts +208 -0
- package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
- package/src/runtime-stubs/load-secrets.ts +40 -0
- package/src/server/api-server.ts +34 -0
- package/src/server/mount-routes.ts +68 -0
- package/src/templates/api-skeleton.ts +72 -0
- package/src/templates/claude-md.ts +7 -0
- package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
- package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
- package/src/templates/dashboard-pages/account/index.ts +5 -0
- package/src/templates/dashboard-pages/account/members.tsx +107 -0
- package/src/templates/dashboard-pages/account/profile.tsx +53 -0
- package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
- package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
- package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
- package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
- package/src/templates/dashboard-pages/auth/index.ts +10 -0
- package/src/templates/dashboard-pages/auth/login.tsx +75 -0
- package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
- package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
- package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
- package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
- package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
- package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
- package/src/templates/dashboard-pages/index.ts +22 -0
- package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
- package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
- package/src/templates/dashboard-pages/shell/index.ts +5 -0
- package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
- package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
- package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
- package/src/templates/domain-config.ts +20 -0
- package/src/templates/events-skeleton.ts +24 -0
- package/src/templates/patterns/api/create-with-event.ts +72 -0
- package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
- package/src/templates/patterns/api/paginated-list.ts +74 -0
- package/src/templates/patterns/api/simple-crud.ts +100 -0
- package/src/templates/patterns/api/system-admin.ts +101 -0
- package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
- package/src/templates/patterns/component/index.ts +833 -0
- package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
- package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
- package/src/templates/patterns/flow/human-in-loop.ts +93 -0
- package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
- package/src/templates/patterns/hook/index.ts +382 -0
- package/src/templates/patterns/migration/column-add.ts +54 -0
- package/src/templates/patterns/migration/event-store.ts +99 -0
- package/src/templates/patterns/migration/index.ts +54 -0
- package/src/templates/patterns/migration/rls-policy.ts +80 -0
- package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
- package/src/templates/patterns/page/dashboard.ts +164 -0
- package/src/templates/patterns/page/detail.ts +162 -0
- package/src/templates/patterns/page/form.ts +192 -0
- package/src/templates/patterns/page/index.ts +17 -0
- package/src/templates/patterns/page/list.ts +138 -0
- package/src/templates/patterns/page/settings.ts +318 -0
- package/src/templates/patterns/page/wizard.ts +293 -0
- package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
- package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
- package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
- package/src/templates/readme.ts +7 -0
- package/src/templates/subscriber-skeleton.ts +28 -0
- package/src/utils/checksum.ts +43 -0
- package/src/utils/file-helpers.ts +91 -0
- package/src/utils/header-inject.ts +126 -0
- package/src/utils/install-file.ts +99 -0
- package/src/utils/logger.ts +26 -0
- package/src/utils/manifest.ts +137 -0
- package/src/utils/s3-fetch.ts +91 -0
- package/src/utils/scaffold-config.ts +172 -0
- package/src/utils/token-substitution.ts +80 -0
- package/src/utils/zod-extractor.ts +31 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { cliLogger } from '../utils/logger.js';
|
|
4
|
+
function toZoneLine(r) {
|
|
5
|
+
return `${r.name}. 300 IN ${r.type} ${r.value}.`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* tib show-dns [--env dev|staging|prod] [--project-dir <path>]
|
|
9
|
+
*
|
|
10
|
+
* Reads the scaffold-config.json and prints DNS records that must be added for
|
|
11
|
+
* custom domain wiring: certificate validation CNAMEs (step 1) and traffic
|
|
12
|
+
* routing CNAMEs (step 2).
|
|
13
|
+
*
|
|
14
|
+
* For the routing CNAME the CloudFront domain is read from the CDK outputs file
|
|
15
|
+
* (.mc/cdk-outputs.json) if available; otherwise it is shown as a placeholder.
|
|
16
|
+
*/
|
|
17
|
+
export async function runShowDns(opts) {
|
|
18
|
+
const projectDir = opts.projectDir ? path.resolve(opts.projectDir) : process.cwd();
|
|
19
|
+
const configPath = path.join(projectDir, '.tib', 'scaffold-config.json');
|
|
20
|
+
let config;
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readFile(configPath, 'utf-8');
|
|
23
|
+
config = JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
console.error('[tib] Could not read .mc/scaffold-config.json — run from a TIB project root.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const environments = config.environments ?? {};
|
|
30
|
+
if (Object.keys(environments).length === 0) {
|
|
31
|
+
console.log('[tib] No environments configured. Set a custom domain via `tib show-dns` or the Setup tab.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Try to load CDK outputs for routing records
|
|
35
|
+
let cdkOutputs = {};
|
|
36
|
+
try {
|
|
37
|
+
const outputsPath = path.join(projectDir, '.tib', 'cdk-outputs.json');
|
|
38
|
+
const raw = await readFile(outputsPath, 'utf-8');
|
|
39
|
+
cdkOutputs = JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// No CDK outputs yet — routing CNAMEs will show a placeholder
|
|
43
|
+
}
|
|
44
|
+
const targetEnvs = opts.env
|
|
45
|
+
? [opts.env]
|
|
46
|
+
: Object.keys(environments);
|
|
47
|
+
for (const env of targetEnvs) {
|
|
48
|
+
const cfg = environments[env];
|
|
49
|
+
if (!cfg?.dashboardUrl) {
|
|
50
|
+
console.log(`\n;; ${env}: no custom domain configured`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const info = await buildEnvDnsInfo(env, cfg.dashboardUrl, cdkOutputs);
|
|
54
|
+
printEnvDns(env, info);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function buildEnvDnsInfo(env, dashboardUrl, cdkOutputs) {
|
|
58
|
+
// Look for cert validation records from CDK outputs
|
|
59
|
+
// Cert validation CNAMEs are emitted by ACM and captured in CDK outputs under the cert resource
|
|
60
|
+
const certArnOutputKey = `tib-cert-${env}-arn`;
|
|
61
|
+
const certArn = Object.values(cdkOutputs)
|
|
62
|
+
.flatMap(stack => Object.entries(stack))
|
|
63
|
+
.find(([k]) => k === certArnOutputKey)?.[1];
|
|
64
|
+
// Look for the CloudFront distribution domain from CDK outputs
|
|
65
|
+
const distributionDomainName = Object.values(cdkOutputs)
|
|
66
|
+
.flatMap(stack => Object.entries(stack))
|
|
67
|
+
.find(([k]) => k.toLowerCase().includes('distributiondomain') && k.toLowerCase().includes(env))?.[1]
|
|
68
|
+
?? `<run cdk synth — distribution domain not yet available>`;
|
|
69
|
+
const validationRecords = certArn
|
|
70
|
+
// When CDK outputs include validation record overrides, they'd be here.
|
|
71
|
+
// Without a live describe-certificate call, we surface a placeholder.
|
|
72
|
+
? [{ type: 'CNAME', name: `_acm-validation.${dashboardUrl}`, value: `<see ACM console for cert ${certArn}>` }]
|
|
73
|
+
: [{ type: 'CNAME', name: `_acm-validation.${dashboardUrl}`, value: `<deploy the FrontendCustomDomainStack first>` }];
|
|
74
|
+
const routingRecords = [
|
|
75
|
+
{ type: 'CNAME', name: dashboardUrl, value: distributionDomainName },
|
|
76
|
+
];
|
|
77
|
+
return {
|
|
78
|
+
dashboardUrl,
|
|
79
|
+
status: certArn ? 'deployed' : 'not_deployed',
|
|
80
|
+
validationRecords,
|
|
81
|
+
routingRecords,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function printEnvDns(env, info) {
|
|
85
|
+
console.log(`\n;; ${env} — ${info.dashboardUrl}`);
|
|
86
|
+
if (info.validationRecords.length > 0) {
|
|
87
|
+
console.log(';; Certificate validation records (add once, shared across deploys)');
|
|
88
|
+
for (const r of info.validationRecords) {
|
|
89
|
+
console.log(toZoneLine(r));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (info.routingRecords.length > 0) {
|
|
93
|
+
console.log(';; Traffic routing records (update when CloudFront domain changes)');
|
|
94
|
+
for (const r of info.routingRecords) {
|
|
95
|
+
console.log(toZoneLine(r));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (info.errorMessage) {
|
|
99
|
+
cliLogger.error({ env, errorMessage: info.errorMessage }, 'DNS info error');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command to show a structured JSON summary of a domain's structure.
|
|
3
|
+
* Reads the domains/<id>/ directory and builds a summary of primitives,
|
|
4
|
+
* tables, events, and files.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Options for the show command.
|
|
8
|
+
*/
|
|
9
|
+
export interface ShowDomainOptions {
|
|
10
|
+
/** Domain ID in kebab-case. */
|
|
11
|
+
domain: string;
|
|
12
|
+
/** Override directory for domains. */
|
|
13
|
+
domainsDir?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Represents a primitive entry (API, subscriber, action, or job).
|
|
17
|
+
*/
|
|
18
|
+
interface PrimitiveEntry {
|
|
19
|
+
type: 'api' | 'subscriber' | 'action' | 'job';
|
|
20
|
+
id: string;
|
|
21
|
+
file: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Represents a domain summary.
|
|
25
|
+
*/
|
|
26
|
+
interface DomainSummary {
|
|
27
|
+
domain: string;
|
|
28
|
+
primitives: {
|
|
29
|
+
apis: PrimitiveEntry[];
|
|
30
|
+
subscribers: PrimitiveEntry[];
|
|
31
|
+
actions: PrimitiveEntry[];
|
|
32
|
+
jobs: PrimitiveEntry[];
|
|
33
|
+
};
|
|
34
|
+
tables: string[];
|
|
35
|
+
events: string[];
|
|
36
|
+
files: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute the show domain command.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runShowDomain(opts: ShowDomainOptions): Promise<DomainSummary>;
|
|
42
|
+
/**
|
|
43
|
+
* CLI entry point: print the structured JSON to stdout.
|
|
44
|
+
*/
|
|
45
|
+
export declare function runShowDomainCli(opts: ShowDomainOptions): Promise<void>;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command to show a structured JSON summary of a domain's structure.
|
|
3
|
+
* Reads the domains/<id>/ directory and builds a summary of primitives,
|
|
4
|
+
* tables, events, and files.
|
|
5
|
+
*/
|
|
6
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { cliLogger } from '../utils/logger.js';
|
|
9
|
+
const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
|
|
10
|
+
/**
|
|
11
|
+
* Recursively list all files in a directory, excluding node_modules and __tests__.
|
|
12
|
+
*/
|
|
13
|
+
async function listFilesRecursive(dir) {
|
|
14
|
+
const result = [];
|
|
15
|
+
async function walk(current, base) {
|
|
16
|
+
try {
|
|
17
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const fullPath = join(current, entry.name);
|
|
20
|
+
const relativePath = join(base, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
if (entry.name === 'node_modules' || entry.name === '__tests__')
|
|
23
|
+
continue;
|
|
24
|
+
await walk(fullPath, relativePath);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
result.push(relativePath.replace(/\\/g, '/'));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Silently skip inaccessible directories
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
await walk(dir, '');
|
|
36
|
+
return result.sort();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Scan a subdirectory for TypeScript primitives.
|
|
40
|
+
*/
|
|
41
|
+
async function scanPrimitives(dir, type) {
|
|
42
|
+
const results = [];
|
|
43
|
+
try {
|
|
44
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (entry.isFile() && entry.name.endsWith('.ts')) {
|
|
47
|
+
const id = entry.name.replace(/\.ts$/, '');
|
|
48
|
+
results.push({
|
|
49
|
+
type,
|
|
50
|
+
id,
|
|
51
|
+
file: `${type === 'subscriber' ? 'subscribers' : type + 's'}/${entry.name}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Directory doesn't exist — no primitives of this type
|
|
58
|
+
}
|
|
59
|
+
return results.sort((a, b) => a.id.localeCompare(b.id));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Execute the show domain command.
|
|
63
|
+
*/
|
|
64
|
+
export async function runShowDomain(opts) {
|
|
65
|
+
if (!KEBAB_REGEX.test(opts.domain)) {
|
|
66
|
+
throw new Error(`Invalid domain id "${opts.domain}" — must be kebab-case`);
|
|
67
|
+
}
|
|
68
|
+
const root = opts.domainsDir ?? resolve(process.cwd(), 'domains');
|
|
69
|
+
const domainDir = join(root, opts.domain);
|
|
70
|
+
try {
|
|
71
|
+
await stat(domainDir);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`Domain "${opts.domain}" not found at ${domainDir}`);
|
|
75
|
+
}
|
|
76
|
+
// Scan primitives
|
|
77
|
+
const apis = await scanPrimitives(join(domainDir, 'api'), 'api');
|
|
78
|
+
const subscribers = await scanPrimitives(join(domainDir, 'subscribers'), 'subscriber');
|
|
79
|
+
const actions = await scanPrimitives(join(domainDir, 'actions'), 'action');
|
|
80
|
+
const jobs = await scanPrimitives(join(domainDir, 'jobs'), 'job');
|
|
81
|
+
// Scan migrations for table definitions
|
|
82
|
+
const tables = [];
|
|
83
|
+
try {
|
|
84
|
+
const migrationDir = join(domainDir, 'db', 'migrations');
|
|
85
|
+
const migrationFiles = await readdir(migrationDir, { withFileTypes: true });
|
|
86
|
+
for (const entry of migrationFiles) {
|
|
87
|
+
if (entry.isFile() && entry.name.endsWith('.sql')) {
|
|
88
|
+
tables.push(entry.name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
tables.sort();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// No migrations directory
|
|
95
|
+
}
|
|
96
|
+
// Scan events
|
|
97
|
+
const events = [];
|
|
98
|
+
try {
|
|
99
|
+
const publishesDir = join(domainDir, 'publishes');
|
|
100
|
+
const publishFiles = await readdir(publishesDir, { withFileTypes: true });
|
|
101
|
+
for (const entry of publishFiles) {
|
|
102
|
+
if (entry.isFile() && entry.name.endsWith('.ts')) {
|
|
103
|
+
events.push(entry.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
events.sort();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// No publishes directory
|
|
110
|
+
}
|
|
111
|
+
// List all files
|
|
112
|
+
const files = await listFilesRecursive(domainDir);
|
|
113
|
+
const summary = {
|
|
114
|
+
domain: opts.domain,
|
|
115
|
+
primitives: {
|
|
116
|
+
apis,
|
|
117
|
+
subscribers,
|
|
118
|
+
actions,
|
|
119
|
+
jobs,
|
|
120
|
+
},
|
|
121
|
+
tables,
|
|
122
|
+
events,
|
|
123
|
+
files,
|
|
124
|
+
};
|
|
125
|
+
cliLogger.info({ domain: opts.domain }, 'Domain summary generated');
|
|
126
|
+
return summary;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* CLI entry point: print the structured JSON to stdout.
|
|
130
|
+
*/
|
|
131
|
+
export async function runShowDomainCli(opts) {
|
|
132
|
+
const summary = await runShowDomain(opts);
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
135
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for the test command.
|
|
3
|
+
*/
|
|
4
|
+
export interface TestOptions {
|
|
5
|
+
/** Absolute or relative path to the domain root. */
|
|
6
|
+
domainRoot: string;
|
|
7
|
+
/** Handler id to invoke (e.g. 'get-invoice'). */
|
|
8
|
+
handlerId: string;
|
|
9
|
+
/** Absolute or relative path to the fixture JSON file. */
|
|
10
|
+
fixturePath: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Execute the test command: build the registry, find the named handler, load the fixture,
|
|
14
|
+
* hydrate a local dev ctx, invoke the handler, and print results to stdout as JSON.
|
|
15
|
+
* @param options - TestOptions specifying domain root, handler id, and fixture path.
|
|
16
|
+
* @throws If the handler id is not found in the registry or the fixture file cannot be read.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runTest(options: TestOptions): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve, join } from 'node:path';
|
|
3
|
+
import { buildRegistry } from '../builder/build-registry.js';
|
|
4
|
+
import { hydrateLocalCtx } from '../runtime-stubs/hydrate-local-ctx.js';
|
|
5
|
+
import { cliLogger } from '../utils/logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Execute the test command: build the registry, find the named handler, load the fixture,
|
|
8
|
+
* hydrate a local dev ctx, invoke the handler, and print results to stdout as JSON.
|
|
9
|
+
* @param options - TestOptions specifying domain root, handler id, and fixture path.
|
|
10
|
+
* @throws If the handler id is not found in the registry or the fixture file cannot be read.
|
|
11
|
+
*/
|
|
12
|
+
export async function runTest(options) {
|
|
13
|
+
const domainRoot = resolve(options.domainRoot);
|
|
14
|
+
const fixturePath = resolve(options.fixturePath);
|
|
15
|
+
const { registry } = await buildRegistry(domainRoot);
|
|
16
|
+
const allEntries = [
|
|
17
|
+
...registry.apis,
|
|
18
|
+
...registry.webhooks,
|
|
19
|
+
...registry.subscribers,
|
|
20
|
+
...registry.schedules,
|
|
21
|
+
...registry.jobs,
|
|
22
|
+
...registry.actions,
|
|
23
|
+
];
|
|
24
|
+
const entry = allEntries.find(e => e.id === options.handlerId);
|
|
25
|
+
if (!entry) {
|
|
26
|
+
throw new Error(`runTest: handler '${options.handlerId}' not found. Available: ${allEntries.map(e => e.id).join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
if (!entry.handlerFile) {
|
|
29
|
+
throw new Error(`runTest: entry '${entry.id}' has no handlerFile`);
|
|
30
|
+
}
|
|
31
|
+
const fixtureRaw = await readFile(fixturePath, 'utf8');
|
|
32
|
+
const fixture = JSON.parse(fixtureRaw);
|
|
33
|
+
const { ctx, captures } = await hydrateLocalCtx({
|
|
34
|
+
domainRoot,
|
|
35
|
+
tenantId: fixture.ctx?.tenantId,
|
|
36
|
+
actorSub: fixture.ctx?.actorSub,
|
|
37
|
+
});
|
|
38
|
+
const handlerAbsPath = join(domainRoot, entry.handlerFile);
|
|
39
|
+
const mod = await import(handlerAbsPath);
|
|
40
|
+
const def = Object.values(mod).find(v => v && typeof v === 'object' && v['id'] === entry.id);
|
|
41
|
+
if (!def || typeof def['handler'] !== 'function') {
|
|
42
|
+
throw new Error(`runTest: no handler function found for '${entry.id}' in ${handlerAbsPath}`);
|
|
43
|
+
}
|
|
44
|
+
const handler = def['handler'];
|
|
45
|
+
cliLogger.info({ handlerId: entry.id, kind: entry.kind }, 'Invoking handler');
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await handler(fixture.event, ctx);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
cliLogger.error({ err }, 'Handler threw an error');
|
|
52
|
+
process.stdout.write(JSON.stringify({ error: String(err), captures }, null, 2) + '\n');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write(JSON.stringify({ result, captures }, null, 2) + '\n');
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface UpgradeBackendOptions {
|
|
2
|
+
/** Target major version, e.g. '2' */
|
|
3
|
+
targetMajor: string;
|
|
4
|
+
/** Root of the repo (defaults to cwd) */
|
|
5
|
+
repoRoot?: string;
|
|
6
|
+
/** Dry run — print changes without applying */
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function runUpgradeBackend(opts: UpgradeBackendOptions): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
const MIGRATIONS = [
|
|
4
|
+
{
|
|
5
|
+
fromMajor: 1,
|
|
6
|
+
toMajor: 2,
|
|
7
|
+
description: 'Migrate defineApi from v1 to v2: rename `versions.v1.handler` shape',
|
|
8
|
+
transform(repoRoot, dryRun) {
|
|
9
|
+
// Placeholder: real migration uses ts-morph to rewrite handler signatures
|
|
10
|
+
console.log(`[G20] Would apply: defineApi v1→v2 migration in ${repoRoot}/domains/**`);
|
|
11
|
+
if (!dryRun) {
|
|
12
|
+
console.log('[G20] ts-morph transform: (not yet implemented — add jscodeshift transforms here)');
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
export async function runUpgradeBackend(opts) {
|
|
18
|
+
const { targetMajor, repoRoot = process.cwd(), dryRun = false } = opts;
|
|
19
|
+
const target = parseInt(targetMajor, 10);
|
|
20
|
+
if (isNaN(target) || target < 1) {
|
|
21
|
+
throw new Error(`Invalid target major version: ${targetMajor}`);
|
|
22
|
+
}
|
|
23
|
+
// Detect current major from package.json
|
|
24
|
+
const pkgPath = join(repoRoot, 'packages', 'domain-runtime', 'package.json');
|
|
25
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
26
|
+
const currentMajor = parseInt(pkg.version.split('.')[0] ?? '1', 10);
|
|
27
|
+
if (currentMajor >= target) {
|
|
28
|
+
console.log(`Already at v${currentMajor} — nothing to upgrade.`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const applicableMigrations = MIGRATIONS.filter(m => m.fromMajor >= currentMajor && m.toMajor <= target);
|
|
32
|
+
if (applicableMigrations.length === 0) {
|
|
33
|
+
console.log(`No migrations found from v${currentMajor} to v${target}.`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const migration of applicableMigrations) {
|
|
37
|
+
console.log(`Applying: v${migration.fromMajor} → v${migration.toMajor}: ${migration.description}`);
|
|
38
|
+
migration.transform(repoRoot, dryRun);
|
|
39
|
+
}
|
|
40
|
+
if (!dryRun) {
|
|
41
|
+
console.log(`\nUpgrade to v${target} complete. Run: npm run lint && npm test`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(`\n[dry-run] No files changed.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command to upgrade an event definition to a new version.
|
|
3
|
+
* Reads the existing event definition from publishes/events.ts,
|
|
4
|
+
* adds a new version entry with proper sunset semantics on the previous version.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Options for the upgrade-event command.
|
|
8
|
+
*/
|
|
9
|
+
export interface UpgradeEventOptions {
|
|
10
|
+
/** Domain ID in kebab-case. */
|
|
11
|
+
domain: string;
|
|
12
|
+
/** Event ID (e.g., 'domain.event'). */
|
|
13
|
+
eventId: string;
|
|
14
|
+
/** Target version number. */
|
|
15
|
+
toVersion: number;
|
|
16
|
+
/** Override directory for domain root. */
|
|
17
|
+
domainsDir?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Execute the upgrade-event command: bump an event definition to a new version.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runUpgradeEvent(opts: UpgradeEventOptions): Promise<void>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command to upgrade an event definition to a new version.
|
|
3
|
+
* Reads the existing event definition from publishes/events.ts,
|
|
4
|
+
* adds a new version entry with proper sunset semantics on the previous version.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { cliLogger } from '../utils/logger.js';
|
|
9
|
+
const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
|
|
10
|
+
/**
|
|
11
|
+
* Execute the upgrade-event command: bump an event definition to a new version.
|
|
12
|
+
*/
|
|
13
|
+
export async function runUpgradeEvent(opts) {
|
|
14
|
+
if (!KEBAB_REGEX.test(opts.domain)) {
|
|
15
|
+
throw new Error(`Invalid domain id "${opts.domain}" — must be kebab-case`);
|
|
16
|
+
}
|
|
17
|
+
if (!Number.isInteger(opts.toVersion) || opts.toVersion < 2) {
|
|
18
|
+
throw new Error(`--to-version must be an integer >= 2, got "${opts.toVersion}"`);
|
|
19
|
+
}
|
|
20
|
+
const root = opts.domainsDir ?? resolve(process.cwd(), 'domains');
|
|
21
|
+
const domainDir = join(root, opts.domain);
|
|
22
|
+
const eventsFilePath = join(domainDir, 'publishes', 'events.ts');
|
|
23
|
+
// Verify events file exists
|
|
24
|
+
try {
|
|
25
|
+
await access(eventsFilePath);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error(`Events file not found at ${eventsFilePath}. Run add-domain first.`);
|
|
29
|
+
}
|
|
30
|
+
const content = await readFile(eventsFilePath, 'utf8');
|
|
31
|
+
const newContent = upgradeEventInSource(content, opts.eventId, opts.toVersion, opts.domain);
|
|
32
|
+
if (newContent === content) {
|
|
33
|
+
throw new Error(`Event "${opts.eventId}" not found in ${eventsFilePath}, or version ${opts.toVersion - 1} not found.`);
|
|
34
|
+
}
|
|
35
|
+
await writeFile(eventsFilePath, newContent);
|
|
36
|
+
cliLogger.info({ domain: opts.domain, eventId: opts.eventId, toVersion: opts.toVersion }, 'Event upgraded');
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log(`\n✓ Event "${opts.eventId}" upgraded to version ${opts.toVersion} in ${eventsFilePath}`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Upgrade an event definition in-source by adding a new version entry
|
|
42
|
+
* and marking the previous version as deprecated with a sunset date.
|
|
43
|
+
*/
|
|
44
|
+
function upgradeEventInSource(content, eventId, toVersion, domain) {
|
|
45
|
+
const prevVersion = toVersion - 1;
|
|
46
|
+
const sunsetDate = new Date(Date.now() + 180 * 86400 * 1000).toISOString().split('T')[0]; // 180 days from now
|
|
47
|
+
// Find the event definition block
|
|
48
|
+
// Pattern: export const eventName = defineEvent({ ... versions: [ ... ] ... });
|
|
49
|
+
const eventPattern = new RegExp(`(export\\s+const\\s+\\w+\\s*=\\s*defineEvent\\(\\{[^}]*id:\\s*['"]${escapeRegex(eventId)}['"][^}]*versions:\\s*\\[)([^\\]]*)(\\][^}]*\\}\\);)`, 's');
|
|
50
|
+
const match = eventPattern.exec(content);
|
|
51
|
+
if (!match) {
|
|
52
|
+
// Try without the export name assumption — search for the eventId
|
|
53
|
+
const altPattern = new RegExp(`('${escapeRegex(eventId)}'|"${escapeRegex(eventId)}")\\s*,\\s*\\n?\\s*versions:\\s*\\[`, 's');
|
|
54
|
+
const altMatch = altPattern.exec(content);
|
|
55
|
+
if (!altMatch) {
|
|
56
|
+
return content; // Not found — caller will detect unchanged content
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Found event "${eventId}" but could not parse its versions array. Manual edit required.`);
|
|
59
|
+
}
|
|
60
|
+
const beforeVersions = match[1];
|
|
61
|
+
const versionsContent = match[2];
|
|
62
|
+
const afterVersions = match[3];
|
|
63
|
+
// Check if the target version already exists
|
|
64
|
+
if (versionsContent.includes(`version: ${toVersion}`)) {
|
|
65
|
+
throw new Error(`Event "${eventId}" already has version ${toVersion}.`);
|
|
66
|
+
}
|
|
67
|
+
// Find the previous version and mark it deprecated
|
|
68
|
+
let updatedVersions = versionsContent;
|
|
69
|
+
// Add sunset to the previous version entry
|
|
70
|
+
const prevVersionRegex = new RegExp(`(version:\\s*${prevVersion}\\s*,[^}]*?)(\\s*\\})`, 's');
|
|
71
|
+
const prevMatch = prevVersionRegex.exec(updatedVersions);
|
|
72
|
+
if (prevMatch) {
|
|
73
|
+
// Check if already has status
|
|
74
|
+
if (!prevMatch[1].includes('status:')) {
|
|
75
|
+
updatedVersions = updatedVersions.replace(prevMatch[0], prevMatch[1].replace(`version: ${prevVersion},`, `version: ${prevVersion},\n status: 'deprecated',\n sunset: '${sunsetDate}',`) + prevMatch[2]);
|
|
76
|
+
}
|
|
77
|
+
else if (!prevMatch[1].includes('sunset:')) {
|
|
78
|
+
updatedVersions = updatedVersions.replace(prevMatch[0], prevMatch[1].replace(/status:\s*'stable'/, "status: 'deprecated'") + `\n sunset: '${sunsetDate}',` + prevMatch[2]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
throw new Error(`Could not find version ${prevVersion} in event "${eventId}".`);
|
|
83
|
+
}
|
|
84
|
+
// Add the new version entry
|
|
85
|
+
const newVersionEntry = `,
|
|
86
|
+
{
|
|
87
|
+
version: ${toVersion},
|
|
88
|
+
status: 'stable',
|
|
89
|
+
schema: z.object({
|
|
90
|
+
// TODO: Define the v${toVersion} schema — extend or replace v${prevVersion}
|
|
91
|
+
}),
|
|
92
|
+
}`;
|
|
93
|
+
// Insert before the closing bracket of the versions array
|
|
94
|
+
const closingBracketIndex = updatedVersions.lastIndexOf(']');
|
|
95
|
+
if (closingBracketIndex === -1) {
|
|
96
|
+
throw new Error(`Could not find closing bracket of versions array for event "${eventId}".`);
|
|
97
|
+
}
|
|
98
|
+
updatedVersions =
|
|
99
|
+
updatedVersions.slice(0, closingBracketIndex) +
|
|
100
|
+
newVersionEntry +
|
|
101
|
+
'\n ' +
|
|
102
|
+
updatedVersions.slice(closingBracketIndex);
|
|
103
|
+
const result = beforeVersions + updatedVersions + afterVersions;
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Escape special regex characters in a string.
|
|
108
|
+
*/
|
|
109
|
+
function escapeRegex(s) {
|
|
110
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
111
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { cliLogger } from '../utils/logger.js';
|
|
4
|
+
const TIB_PACKAGES = [
|
|
5
|
+
'@mettlecast/design-system',
|
|
6
|
+
'@mettlecast/dashboard-shell',
|
|
7
|
+
'@mettlecast/sdk',
|
|
8
|
+
'@mettlecast/observability',
|
|
9
|
+
'@mettlecast/eslint-config-react',
|
|
10
|
+
'@mettlecast/tsconfig-react',
|
|
11
|
+
];
|
|
12
|
+
const REQUIRED_PACKAGES = [
|
|
13
|
+
'@mettlecast/design-system',
|
|
14
|
+
'@mettlecast/dashboard-shell',
|
|
15
|
+
'@mettlecast/sdk',
|
|
16
|
+
];
|
|
17
|
+
const RECOMMENDED_PACKAGES = [
|
|
18
|
+
'@mettlecast/observability',
|
|
19
|
+
'@mettlecast/eslint-config-react',
|
|
20
|
+
'@mettlecast/tsconfig-react',
|
|
21
|
+
];
|
|
22
|
+
function allDeps(pkg) {
|
|
23
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
24
|
+
}
|
|
25
|
+
export async function runUpgradeFrontend(opts = {}) {
|
|
26
|
+
const root = opts.projectDir ? resolve(opts.projectDir) : process.cwd();
|
|
27
|
+
const pkgPath = resolve(root, 'package.json');
|
|
28
|
+
let pkg;
|
|
29
|
+
try {
|
|
30
|
+
const raw = await readFile(pkgPath, 'utf8');
|
|
31
|
+
pkg = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`Could not read package.json at ${pkgPath}`);
|
|
35
|
+
}
|
|
36
|
+
const deps = allDeps(pkg);
|
|
37
|
+
const results = [];
|
|
38
|
+
for (const p of REQUIRED_PACKAGES) {
|
|
39
|
+
const installed = deps[p];
|
|
40
|
+
if (!installed) {
|
|
41
|
+
results.push({ pkg: p, status: 'missing', message: `Not installed — required. Add to dependencies.` });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
results.push({ pkg: p, status: 'ok', installedVersion: installed, message: `Installed at ${installed}` });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const p of RECOMMENDED_PACKAGES) {
|
|
48
|
+
const installed = deps[p];
|
|
49
|
+
if (!installed) {
|
|
50
|
+
results.push({ pkg: p, status: 'recommended', message: `Not installed — recommended for full functionality.` });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
results.push({ pkg: p, status: 'ok', installedVersion: installed, message: `Installed at ${installed}` });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Print results
|
|
57
|
+
const symbols = {
|
|
58
|
+
ok: '✓',
|
|
59
|
+
missing: '✗',
|
|
60
|
+
outdated: '⚠',
|
|
61
|
+
recommended: '·',
|
|
62
|
+
};
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.log(`\nTIB frontend upgrade check for ${pkg.name ?? root}\n`);
|
|
65
|
+
let hasErrors = false;
|
|
66
|
+
for (const r of results) {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.log(` ${symbols[r.status]} ${r.pkg.padEnd(32)} ${r.message}`);
|
|
69
|
+
if (r.status === 'missing')
|
|
70
|
+
hasErrors = true;
|
|
71
|
+
}
|
|
72
|
+
const missingCount = results.filter(r => r.status === 'missing').length;
|
|
73
|
+
const recommendedCount = results.filter(r => r.status === 'recommended').length;
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log('');
|
|
76
|
+
if (missingCount > 0) {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.log(` ${missingCount} required package(s) missing. Run:\n`);
|
|
79
|
+
const missing = results.filter(r => r.status === 'missing').map(r => r.pkg);
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.log(` npm install ${missing.join(' ')}\n`);
|
|
82
|
+
}
|
|
83
|
+
if (recommendedCount > 0) {
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.log(` ${recommendedCount} recommended package(s) not installed. See docs/migration/v0-to-v1.md\n`);
|
|
86
|
+
}
|
|
87
|
+
if (!hasErrors && missingCount === 0) {
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.log(' All required @mettlecast/* packages are installed.\n');
|
|
90
|
+
}
|
|
91
|
+
if (opts.dryRun) {
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.log(' (dry-run mode — no changes made)\n');
|
|
94
|
+
}
|
|
95
|
+
cliLogger.info({ missingCount, recommendedCount }, 'upgrade-frontend check complete');
|
|
96
|
+
if (hasErrors && !opts.dryRun) {
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|