@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.
Files changed (301) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +133 -0
  3. package/dist/builder/build-registry.d.ts +20 -0
  4. package/dist/builder/build-registry.js +178 -0
  5. package/dist/builder/build-types.d.ts +10 -0
  6. package/dist/builder/build-types.js +32 -0
  7. package/dist/builder/load-domain-module-config.d.ts +12 -0
  8. package/dist/builder/load-domain-module-config.js +34 -0
  9. package/dist/builder/load-module.d.ts +26 -0
  10. package/dist/builder/load-module.js +114 -0
  11. package/dist/builder/zod-to-json.d.ts +10 -0
  12. package/dist/builder/zod-to-json.js +15 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +338 -0
  15. package/dist/commands/add-api.d.ts +32 -0
  16. package/dist/commands/add-api.js +106 -0
  17. package/dist/commands/add-component.d.ts +15 -0
  18. package/dist/commands/add-component.js +78 -0
  19. package/dist/commands/add-domain.d.ts +21 -0
  20. package/dist/commands/add-domain.js +71 -0
  21. package/dist/commands/add-flow.d.ts +14 -0
  22. package/dist/commands/add-flow.js +108 -0
  23. package/dist/commands/add-hook.d.ts +15 -0
  24. package/dist/commands/add-hook.js +65 -0
  25. package/dist/commands/add-migration.d.ts +26 -0
  26. package/dist/commands/add-migration.js +93 -0
  27. package/dist/commands/add-module.d.ts +7 -0
  28. package/dist/commands/add-module.js +261 -0
  29. package/dist/commands/add-page.d.ts +13 -0
  30. package/dist/commands/add-page.js +170 -0
  31. package/dist/commands/add-seed-page.d.ts +5 -0
  32. package/dist/commands/add-seed-page.js +251 -0
  33. package/dist/commands/add-subscriber.d.ts +30 -0
  34. package/dist/commands/add-subscriber.js +87 -0
  35. package/dist/commands/add-test.d.ts +22 -0
  36. package/dist/commands/add-test.js +285 -0
  37. package/dist/commands/audit.d.ts +39 -0
  38. package/dist/commands/audit.js +171 -0
  39. package/dist/commands/build-catalog.d.ts +68 -0
  40. package/dist/commands/build-catalog.js +105 -0
  41. package/dist/commands/build-flows.d.ts +26 -0
  42. package/dist/commands/build-flows.js +174 -0
  43. package/dist/commands/build.d.ts +17 -0
  44. package/dist/commands/build.js +39 -0
  45. package/dist/commands/check-hashes.d.ts +38 -0
  46. package/dist/commands/check-hashes.js +97 -0
  47. package/dist/commands/create-project.d.ts +5 -0
  48. package/dist/commands/create-project.js +272 -0
  49. package/dist/commands/dev.d.ts +15 -0
  50. package/dist/commands/dev.js +27 -0
  51. package/dist/commands/doctor.d.ts +48 -0
  52. package/dist/commands/doctor.js +1301 -0
  53. package/dist/commands/explain.d.ts +34 -0
  54. package/dist/commands/explain.js +167 -0
  55. package/dist/commands/power-tune.d.ts +1 -0
  56. package/dist/commands/power-tune.js +87 -0
  57. package/dist/commands/reseed-page.d.ts +6 -0
  58. package/dist/commands/reseed-page.js +297 -0
  59. package/dist/commands/show-dns.d.ts +15 -0
  60. package/dist/commands/show-dns.js +101 -0
  61. package/dist/commands/show.d.ts +46 -0
  62. package/dist/commands/show.js +135 -0
  63. package/dist/commands/test.d.ts +18 -0
  64. package/dist/commands/test.js +56 -0
  65. package/dist/commands/upgrade-backend.d.ts +9 -0
  66. package/dist/commands/upgrade-backend.js +46 -0
  67. package/dist/commands/upgrade-event.d.ts +22 -0
  68. package/dist/commands/upgrade-event.js +111 -0
  69. package/dist/commands/upgrade-frontend.d.ts +5 -0
  70. package/dist/commands/upgrade-frontend.js +99 -0
  71. package/dist/commands/upgrade.d.ts +7 -0
  72. package/dist/commands/upgrade.js +495 -0
  73. package/dist/commands/validate.d.ts +30 -0
  74. package/dist/commands/validate.js +148 -0
  75. package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
  76. package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
  77. package/dist/runtime-stubs/load-secrets.d.ts +11 -0
  78. package/dist/runtime-stubs/load-secrets.js +37 -0
  79. package/dist/server/api-server.d.ts +21 -0
  80. package/dist/server/api-server.js +14 -0
  81. package/dist/server/mount-routes.d.ts +19 -0
  82. package/dist/server/mount-routes.js +42 -0
  83. package/dist/templates/api-skeleton.d.ts +18 -0
  84. package/dist/templates/api-skeleton.js +69 -0
  85. package/dist/templates/claude-md.d.ts +4 -0
  86. package/dist/templates/claude-md.js +7 -0
  87. package/dist/templates/dashboard-pages/index.d.ts +101 -0
  88. package/dist/templates/dashboard-pages/index.js +22 -0
  89. package/dist/templates/domain-config.d.ts +10 -0
  90. package/dist/templates/domain-config.js +19 -0
  91. package/dist/templates/events-skeleton.d.ts +9 -0
  92. package/dist/templates/events-skeleton.js +23 -0
  93. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  94. package/dist/templates/patterns/api/create-with-event.js +69 -0
  95. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  96. package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
  97. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  98. package/dist/templates/patterns/api/paginated-list.js +71 -0
  99. package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
  100. package/dist/templates/patterns/api/simple-crud.js +95 -0
  101. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  102. package/dist/templates/patterns/api/system-admin.js +98 -0
  103. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  104. package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
  105. package/dist/templates/patterns/component/index.d.ts +25 -0
  106. package/dist/templates/patterns/component/index.js +814 -0
  107. package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
  108. package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
  109. package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
  110. package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
  111. package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
  112. package/dist/templates/patterns/flow/human-in-loop.js +90 -0
  113. package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
  114. package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
  115. package/dist/templates/patterns/hook/index.d.ts +7 -0
  116. package/dist/templates/patterns/hook/index.js +378 -0
  117. package/dist/templates/patterns/migration/column-add.d.ts +5 -0
  118. package/dist/templates/patterns/migration/column-add.js +52 -0
  119. package/dist/templates/patterns/migration/event-store.d.ts +6 -0
  120. package/dist/templates/patterns/migration/event-store.js +97 -0
  121. package/dist/templates/patterns/migration/index.d.ts +5 -0
  122. package/dist/templates/patterns/migration/index.js +52 -0
  123. package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
  124. package/dist/templates/patterns/migration/rls-policy.js +78 -0
  125. package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
  126. package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
  127. package/dist/templates/patterns/page/dashboard.d.ts +4 -0
  128. package/dist/templates/patterns/page/dashboard.js +164 -0
  129. package/dist/templates/patterns/page/detail.d.ts +4 -0
  130. package/dist/templates/patterns/page/detail.js +162 -0
  131. package/dist/templates/patterns/page/form.d.ts +4 -0
  132. package/dist/templates/patterns/page/form.js +192 -0
  133. package/dist/templates/patterns/page/index.d.ts +8 -0
  134. package/dist/templates/patterns/page/index.js +14 -0
  135. package/dist/templates/patterns/page/list.d.ts +4 -0
  136. package/dist/templates/patterns/page/list.js +138 -0
  137. package/dist/templates/patterns/page/settings.d.ts +4 -0
  138. package/dist/templates/patterns/page/settings.js +318 -0
  139. package/dist/templates/patterns/page/wizard.d.ts +4 -0
  140. package/dist/templates/patterns/page/wizard.js +293 -0
  141. package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
  142. package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
  143. package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
  144. package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
  145. package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
  146. package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
  147. package/dist/templates/readme.d.ts +4 -0
  148. package/dist/templates/readme.js +7 -0
  149. package/dist/templates/subscriber-skeleton.d.ts +11 -0
  150. package/dist/templates/subscriber-skeleton.js +23 -0
  151. package/dist/utils/checksum.d.ts +18 -0
  152. package/dist/utils/checksum.js +39 -0
  153. package/dist/utils/file-helpers.d.ts +35 -0
  154. package/dist/utils/file-helpers.js +59 -0
  155. package/dist/utils/header-inject.d.ts +15 -0
  156. package/dist/utils/header-inject.js +93 -0
  157. package/dist/utils/install-file.d.ts +17 -0
  158. package/dist/utils/install-file.js +78 -0
  159. package/dist/utils/logger.d.ts +12 -0
  160. package/dist/utils/logger.js +24 -0
  161. package/dist/utils/manifest.d.ts +28 -0
  162. package/dist/utils/manifest.js +93 -0
  163. package/dist/utils/s3-fetch.d.ts +25 -0
  164. package/dist/utils/s3-fetch.js +52 -0
  165. package/dist/utils/scaffold-config.d.ts +95 -0
  166. package/dist/utils/scaffold-config.js +53 -0
  167. package/dist/utils/token-substitution.d.ts +35 -0
  168. package/dist/utils/token-substitution.js +52 -0
  169. package/dist/utils/zod-extractor.d.ts +10 -0
  170. package/dist/utils/zod-extractor.js +28 -0
  171. package/package.json +43 -0
  172. package/src/__tests__/build-registry.test.ts +115 -0
  173. package/src/__tests__/build-types.test.ts +100 -0
  174. package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
  175. package/src/__tests__/commands/add-domain.test.ts +174 -0
  176. package/src/__tests__/commands/add-flow.test.ts +69 -0
  177. package/src/__tests__/commands/add-module.test.ts +207 -0
  178. package/src/__tests__/commands/build-flows.test.ts +154 -0
  179. package/src/__tests__/commands/check-hashes.test.ts +142 -0
  180. package/src/__tests__/commands/create-project.test.ts +712 -0
  181. package/src/__tests__/commands/upgrade.test.ts +762 -0
  182. package/src/__tests__/doctor.test.ts +291 -0
  183. package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
  184. package/src/__tests__/load-domain-module-config.test.ts +10 -0
  185. package/src/__tests__/mount-routes.test.ts +100 -0
  186. package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
  187. package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
  188. package/src/__tests__/utils/checksum.test.ts +90 -0
  189. package/src/__tests__/utils/header-inject.test.ts +89 -0
  190. package/src/__tests__/utils/install-file.test.ts +207 -0
  191. package/src/__tests__/utils/manifest.test.ts +229 -0
  192. package/src/__tests__/utils/token-substitution.test.ts +134 -0
  193. package/src/__tests__/validate.test.ts +18 -0
  194. package/src/builder/build-registry.ts +237 -0
  195. package/src/builder/build-types.ts +36 -0
  196. package/src/builder/load-domain-module-config.ts +43 -0
  197. package/src/builder/load-module.ts +133 -0
  198. package/src/builder/zod-to-json.ts +16 -0
  199. package/src/cli.ts +387 -0
  200. package/src/commands/add-api.ts +145 -0
  201. package/src/commands/add-component.ts +116 -0
  202. package/src/commands/add-domain.ts +100 -0
  203. package/src/commands/add-flow.ts +136 -0
  204. package/src/commands/add-hook.ts +100 -0
  205. package/src/commands/add-migration.ts +129 -0
  206. package/src/commands/add-module.ts +347 -0
  207. package/src/commands/add-page.ts +242 -0
  208. package/src/commands/add-seed-page.ts +338 -0
  209. package/src/commands/add-subscriber.ts +123 -0
  210. package/src/commands/add-test.ts +328 -0
  211. package/src/commands/audit.ts +217 -0
  212. package/src/commands/build-catalog.ts +181 -0
  213. package/src/commands/build-flows.ts +198 -0
  214. package/src/commands/build.ts +59 -0
  215. package/src/commands/check-hashes.ts +150 -0
  216. package/src/commands/create-project.ts +367 -0
  217. package/src/commands/dev.ts +47 -0
  218. package/src/commands/doctor.ts +1362 -0
  219. package/src/commands/explain.ts +209 -0
  220. package/src/commands/power-tune.ts +112 -0
  221. package/src/commands/reseed-page.ts +410 -0
  222. package/src/commands/show-dns.ts +139 -0
  223. package/src/commands/show.ts +179 -0
  224. package/src/commands/test.ts +91 -0
  225. package/src/commands/upgrade-backend.ts +72 -0
  226. package/src/commands/upgrade-event.ts +175 -0
  227. package/src/commands/upgrade-frontend.ts +135 -0
  228. package/src/commands/upgrade.ts +668 -0
  229. package/src/commands/validate.ts +208 -0
  230. package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
  231. package/src/runtime-stubs/load-secrets.ts +40 -0
  232. package/src/server/api-server.ts +34 -0
  233. package/src/server/mount-routes.ts +68 -0
  234. package/src/templates/api-skeleton.ts +72 -0
  235. package/src/templates/claude-md.ts +7 -0
  236. package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
  237. package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
  238. package/src/templates/dashboard-pages/account/index.ts +5 -0
  239. package/src/templates/dashboard-pages/account/members.tsx +107 -0
  240. package/src/templates/dashboard-pages/account/profile.tsx +53 -0
  241. package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
  242. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
  243. package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
  244. package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
  245. package/src/templates/dashboard-pages/auth/index.ts +10 -0
  246. package/src/templates/dashboard-pages/auth/login.tsx +75 -0
  247. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
  248. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
  249. package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
  250. package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
  251. package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
  252. package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
  253. package/src/templates/dashboard-pages/index.ts +22 -0
  254. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
  255. package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
  256. package/src/templates/dashboard-pages/shell/index.ts +5 -0
  257. package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
  258. package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
  259. package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
  260. package/src/templates/domain-config.ts +20 -0
  261. package/src/templates/events-skeleton.ts +24 -0
  262. package/src/templates/patterns/api/create-with-event.ts +72 -0
  263. package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
  264. package/src/templates/patterns/api/paginated-list.ts +74 -0
  265. package/src/templates/patterns/api/simple-crud.ts +100 -0
  266. package/src/templates/patterns/api/system-admin.ts +101 -0
  267. package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
  268. package/src/templates/patterns/component/index.ts +833 -0
  269. package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
  270. package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
  271. package/src/templates/patterns/flow/human-in-loop.ts +93 -0
  272. package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
  273. package/src/templates/patterns/hook/index.ts +382 -0
  274. package/src/templates/patterns/migration/column-add.ts +54 -0
  275. package/src/templates/patterns/migration/event-store.ts +99 -0
  276. package/src/templates/patterns/migration/index.ts +54 -0
  277. package/src/templates/patterns/migration/rls-policy.ts +80 -0
  278. package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
  279. package/src/templates/patterns/page/dashboard.ts +164 -0
  280. package/src/templates/patterns/page/detail.ts +162 -0
  281. package/src/templates/patterns/page/form.ts +192 -0
  282. package/src/templates/patterns/page/index.ts +17 -0
  283. package/src/templates/patterns/page/list.ts +138 -0
  284. package/src/templates/patterns/page/settings.ts +318 -0
  285. package/src/templates/patterns/page/wizard.ts +293 -0
  286. package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
  287. package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
  288. package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
  289. package/src/templates/readme.ts +7 -0
  290. package/src/templates/subscriber-skeleton.ts +28 -0
  291. package/src/utils/checksum.ts +43 -0
  292. package/src/utils/file-helpers.ts +91 -0
  293. package/src/utils/header-inject.ts +126 -0
  294. package/src/utils/install-file.ts +99 -0
  295. package/src/utils/logger.ts +26 -0
  296. package/src/utils/manifest.ts +137 -0
  297. package/src/utils/s3-fetch.ts +91 -0
  298. package/src/utils/scaffold-config.ts +172 -0
  299. package/src/utils/token-substitution.ts +80 -0
  300. package/src/utils/zod-extractor.ts +31 -0
  301. 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,5 @@
1
+ export interface UpgradeFrontendOptions {
2
+ dryRun?: boolean;
3
+ projectDir?: string;
4
+ }
5
+ export declare function runUpgradeFrontend(opts?: UpgradeFrontendOptions): Promise<void>;
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ export interface UpgradeOptions {
2
+ dryRun: boolean;
3
+ projectDir: string;
4
+ githubToken?: string;
5
+ frontendComponents?: boolean;
6
+ }
7
+ export declare function runUpgrade(packageSpec: string | undefined, opts: UpgradeOptions): Promise<void>;