@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,34 @@
1
+ /**
2
+ * Command to explain a rule ID (ESLint rule name or doctor check name).
3
+ * Maps rule IDs to descriptions and K-node references.
4
+ */
5
+ /**
6
+ * Options for the explain command.
7
+ */
8
+ export interface ExplainOptions {
9
+ /** The rule ID to explain (e.g., 'no-cross-domain-internal-import'). */
10
+ ruleId: string;
11
+ }
12
+ /**
13
+ * A rule explanation entry.
14
+ */
15
+ interface RuleExplanation {
16
+ ruleId: string;
17
+ title: string;
18
+ description: string;
19
+ severity: 'error' | 'warn';
20
+ category: 'convention' | 'security' | 'structure' | 'performance' | 'correctness';
21
+ kNodeRef: string;
22
+ fixHint: string;
23
+ exampleBad: string;
24
+ exampleGood: string;
25
+ }
26
+ /**
27
+ * Execute the explain command: look up a rule ID and print its explanation.
28
+ */
29
+ export declare function runExplain(opts: ExplainOptions): RuleExplanation;
30
+ /**
31
+ * CLI entry point: prints the explanation in a human-readable format.
32
+ */
33
+ export declare function runExplainCli(opts: ExplainOptions): void;
34
+ export {};
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Command to explain a rule ID (ESLint rule name or doctor check name).
3
+ * Maps rule IDs to descriptions and K-node references.
4
+ */
5
+ /**
6
+ * Lookup table of rule IDs to explanations.
7
+ */
8
+ const RULE_EXPLANATIONS = {
9
+ 'no-cross-domain-internal-import': {
10
+ ruleId: 'no-cross-domain-internal-import',
11
+ title: 'No cross-domain internal imports',
12
+ description: 'Domain code must never import from another domain directly. Cross-domain communication must use actions (ctx.actions.call), events (ctx.publish), or defined integrations.',
13
+ severity: 'error',
14
+ category: 'structure',
15
+ kNodeRef: 'K:convention:no-cross-domain-import',
16
+ fixHint: "Replace 'import { ... } from \"../../other-domain/...\"' with 'await ctx.actions.call(\"other-domain.action-id\", input)'.",
17
+ exampleBad: "import { doThing } from '../../payments/api/charge';",
18
+ exampleGood: "await ctx.actions.call('payments.charge', { amount: 100 });",
19
+ },
20
+ 'no-raw-aws-sdk': {
21
+ ruleId: 'no-raw-aws-sdk',
22
+ title: 'No raw AWS SDK imports',
23
+ description: 'Domain code must never import @aws-sdk/* directly. Use ctx-provided helpers (ctx.db, ctx.publish, ctx.secrets, ctx.fetch) or define an integration.',
24
+ severity: 'error',
25
+ category: 'structure',
26
+ kNodeRef: 'K:convention:tier-1-foundations',
27
+ fixHint: "Use ctx.db for DynamoDB, ctx.secrets for Secrets Manager, ctx.fetch for HTTP calls. For other services, define an integration via defineIntegration.",
28
+ exampleBad: "import { S3Client } from '@aws-sdk/client-s3';",
29
+ exampleGood: "await ctx.integrations.s3.putObject({ Bucket, Key, Body });",
30
+ },
31
+ 'no-raw-http-server': {
32
+ ruleId: 'no-raw-http-server',
33
+ title: 'No raw HTTP server in domain code',
34
+ description: 'Domain code must not create raw HTTP servers (express, fastify, etc.). All HTTP handling goes through defineApi which is wired to API Gateway by the scaffold.',
35
+ severity: 'error',
36
+ category: 'structure',
37
+ kNodeRef: 'K:convention:tier-1-foundations',
38
+ fixHint: 'Wrap your HTTP handler with defineApi({ ... }). The scaffold handles API Gateway wiring.',
39
+ exampleBad: "import express from 'express';\nconst app = express();",
40
+ exampleGood: "export const myApi = defineApi({ id: 'my-api', path: '/v1/my-api', ... });",
41
+ },
42
+ 'require-define-primitive': {
43
+ ruleId: 'require-define-primitive',
44
+ title: 'Require define primitive factories',
45
+ description: 'All domain handlers must use the appropriate factory function: defineApi for HTTP APIs, defineSubscriber for event subscribers, defineAction for cross-domain actions, defineJob for background jobs.',
46
+ severity: 'error',
47
+ category: 'structure',
48
+ kNodeRef: 'K:runbook:add-domain',
49
+ fixHint: "Wrap your handler with the appropriate define* factory from @mettlecast/domain-runtime.",
50
+ exampleBad: "export const handler = async (event) => ({ statusCode: 200 });",
51
+ exampleGood: "export const myApi = defineApi({ id: 'my-api', ... });",
52
+ },
53
+ 'flow-domain-ownership': {
54
+ ruleId: 'flow-domain-ownership',
55
+ title: 'Flow domain ownership',
56
+ description: 'Every flow must have an owningDomain. Root-level flows/ directory is deprecated. Flows belong inside domains/{owningDomain}/flows/.',
57
+ severity: 'error',
58
+ category: 'structure',
59
+ kNodeRef: 'K:convention:flow-vs-subscriber-rule',
60
+ fixHint: "Move the flow file to domains/<owningDomain>/flows/<id>.ts and set owningDomain in the defineFlow call.",
61
+ exampleBad: "// flows/onboarding.ts (root level — deprecated)",
62
+ exampleGood: "// domains/auth/flows/onboarding.ts\nexport const onboarding = defineFlow({ owningDomain: 'auth', ... });",
63
+ },
64
+ 'domain-configs-valid': {
65
+ ruleId: 'domain-configs-valid',
66
+ title: 'Domain configs are valid',
67
+ description: 'Every domain directory under domains/ must contain a valid domain.config.ts file exported by defineDomain().',
68
+ severity: 'error',
69
+ category: 'structure',
70
+ kNodeRef: 'K:runbook:add-domain',
71
+ fixHint: "Run 'mc-domain-module add-domain <id>' to scaffold a missing domain.",
72
+ exampleBad: '// domains/payments/ — missing domain.config.ts',
73
+ exampleGood: "// domains/payments/domain.config.ts\nexport const domain = defineDomain({ id: 'payments', tenancy: 'required' });",
74
+ },
75
+ 'apis-have-versions': {
76
+ ruleId: 'apis-have-versions',
77
+ title: 'APIs declare versions',
78
+ description: 'Every defineApi must have a versions field with at least one version entry. Versioning is mandatory for API evolution.',
79
+ severity: 'error',
80
+ category: 'correctness',
81
+ kNodeRef: 'K:runbook:add-domain',
82
+ fixHint: 'Add versions: { v1: { status, input, output, handler } } to your defineApi call.',
83
+ exampleBad: "defineApi({ id: 'my-api', path: '/v1/my-api' }) // missing versions",
84
+ exampleGood: "defineApi({ id: 'my-api', path: '/v1/my-api', versions: { v1: { ... } } });",
85
+ },
86
+ 'apis-have-tenancy': {
87
+ ruleId: 'apis-have-tenancy',
88
+ title: 'APIs declare tenancy',
89
+ description: 'Every defineApi must declare its tenancy mode: required (tenant-scoped), none (tenant-agnostic like registration), or system (system-internal admin).',
90
+ severity: 'error',
91
+ category: 'correctness',
92
+ kNodeRef: 'K:convention:tier-1-foundations',
93
+ fixHint: "Add tenancy: 'required' | 'none' | 'system' to your defineApi call.",
94
+ exampleBad: "defineApi({ id: 'my-api', path: '/v1/my-api' }) // missing tenancy",
95
+ exampleGood: "defineApi({ id: 'my-api', path: '/v1/my-api', tenancy: 'required' });",
96
+ },
97
+ 'no-cross-domain-imports': {
98
+ ruleId: 'no-cross-domain-imports',
99
+ title: 'No cross-domain imports (doctor check)',
100
+ description: 'The doctor command scans for import statements that reference files in other domain directories. These violate domain isolation.',
101
+ severity: 'error',
102
+ category: 'structure',
103
+ kNodeRef: 'K:convention:no-cross-domain-import',
104
+ fixHint: "Use domain actions or events to communicate between domains. See 'tib explain no-cross-domain-internal-import' for details.",
105
+ exampleBad: "import { charge } from '../../payments/api/charge';",
106
+ exampleGood: "await ctx.actions.call('payments.charge', { amount: 100 });",
107
+ },
108
+ 'scaffold-config-matches': {
109
+ ruleId: 'scaffold-config-matches',
110
+ title: 'Scaffold config matches on-disk domains',
111
+ description: 'The .mc/scaffold-config.json domainIds array must match the directories present under domains/. Run add-domain to sync.',
112
+ severity: 'error',
113
+ category: 'structure',
114
+ kNodeRef: 'K:runbook:add-domain',
115
+ fixHint: "Run 'mc-domain-module add-domain <id>' for missing domains, or remove stale entries from .mc/scaffold-config.json.",
116
+ exampleBad: '// domains/ has "billing" but .mc/scaffold-config.json does not list it',
117
+ exampleGood: '// Run: mc-domain-module add-domain billing',
118
+ },
119
+ 'husky-hooks-installed': {
120
+ ruleId: 'husky-hooks-installed',
121
+ title: 'Husky git hooks are installed',
122
+ description: 'The .husky/pre-commit hook should be present and executable. It enforces pre-commit checks like ESLint and commit message format.',
123
+ severity: 'warn',
124
+ category: 'structure',
125
+ kNodeRef: 'K:runbook:add-domain',
126
+ fixHint: "Run 'npx husky install' to set up git hooks.",
127
+ exampleBad: 'Missing .husky/pre-commit',
128
+ exampleGood: 'npx husky install # creates .husky/pre-commit',
129
+ },
130
+ };
131
+ /**
132
+ * Execute the explain command: look up a rule ID and print its explanation.
133
+ */
134
+ export function runExplain(opts) {
135
+ const explanation = RULE_EXPLANATIONS[opts.ruleId];
136
+ if (!explanation) {
137
+ const available = Object.keys(RULE_EXPLANATIONS).join(', ');
138
+ throw new Error(`Unknown rule ID "${opts.ruleId}".\n\nAvailable rules: ${available}`);
139
+ }
140
+ return explanation;
141
+ }
142
+ /**
143
+ * CLI entry point: prints the explanation in a human-readable format.
144
+ */
145
+ export function runExplainCli(opts) {
146
+ const explanation = runExplain(opts);
147
+ // eslint-disable-next-line no-console
148
+ console.log(`
149
+ ╔══════════════════════════════════════════════════════════════╗
150
+ ║ ${explanation.ruleId}
151
+ ╚══════════════════════════════════════════════════════════════╝
152
+
153
+ Severity: ${explanation.severity.toUpperCase()}
154
+ Category: ${explanation.category}
155
+ Docs: ${explanation.kNodeRef}
156
+
157
+ ${explanation.description}
158
+
159
+ Fix: ${explanation.fixHint}
160
+
161
+ ❌ BAD:
162
+ ${explanation.exampleBad.split('\n').map((l) => ' ' + l).join('\n')}
163
+
164
+ ✅ GOOD:
165
+ ${explanation.exampleGood.split('\n').map((l) => ' ' + l).join('\n')}
166
+ `);
167
+ }
@@ -0,0 +1 @@
1
+ export declare function runPowerTune(domain: string, primitive: string): Promise<void>;
@@ -0,0 +1,87 @@
1
+ import { SFNClient, StartExecutionCommand, DescribeExecutionCommand } from '@aws-sdk/client-sfn';
2
+ import { readFileSync } from 'fs';
3
+ import path from 'path';
4
+ import { cliLogger } from '../utils/logger.js';
5
+ export async function runPowerTune(domain, primitive) {
6
+ // Load registry to find Lambda ARN
7
+ const registryPath = path.join(process.cwd(), '.tib', `${domain}-registry.json`);
8
+ let registry;
9
+ try {
10
+ registry = JSON.parse(readFileSync(registryPath, 'utf8'));
11
+ }
12
+ catch {
13
+ cliLogger.error({}, `Registry not found at ${registryPath}. Run: tib build domains/${domain}`);
14
+ process.exit(1);
15
+ }
16
+ // Find Lambda ARN for the primitive
17
+ const lambdaArn = registry.functionArns?.[primitive];
18
+ if (!lambdaArn) {
19
+ cliLogger.error({}, `No Lambda ARN found for primitive '${primitive}' in domain '${domain}'.`);
20
+ cliLogger.error({}, `Available primitives: ${Object.keys(registry.functionArns ?? {}).join(', ')}`);
21
+ process.exit(1);
22
+ }
23
+ // Get state machine ARN
24
+ const stateMachineArn = process.env['POWER_TUNING_STATE_MACHINE_ARN'];
25
+ if (!stateMachineArn) {
26
+ cliLogger.error({}, 'POWER_TUNING_STATE_MACHINE_ARN environment variable not set.');
27
+ cliLogger.error({}, 'Deploy PowerTuningStack first and export the ARN.');
28
+ process.exit(1);
29
+ }
30
+ const sfnClient = new SFNClient({});
31
+ const input = JSON.stringify({
32
+ lambdaARN: lambdaArn,
33
+ powerValues: [128, 256, 512, 1024, 1536, 3008],
34
+ num: 10,
35
+ payload: '{}',
36
+ parallelInvocation: true,
37
+ strategy: 'cost',
38
+ });
39
+ cliLogger.info({ domain, primitive, lambdaArn }, 'Starting power tuning');
40
+ const startResult = await sfnClient.send(new StartExecutionCommand({
41
+ stateMachineArn,
42
+ input,
43
+ name: `tib-power-tune-${domain}-${primitive}-${Date.now()}`,
44
+ }));
45
+ const executionArn = startResult.executionArn;
46
+ cliLogger.info({ executionArn }, 'Execution started');
47
+ cliLogger.info({}, 'Polling for results (this takes 1-3 minutes)...');
48
+ // Poll for completion
49
+ let attempts = 0;
50
+ const maxAttempts = 60; // 5 minutes at 5s intervals
51
+ while (attempts < maxAttempts) {
52
+ await new Promise(resolve => setTimeout(resolve, 5000));
53
+ attempts++;
54
+ const status = await sfnClient.send(new DescribeExecutionCommand({ executionArn }));
55
+ if (status.status === 'RUNNING') {
56
+ process.stdout.write('.');
57
+ continue;
58
+ }
59
+ process.stdout.write('\n');
60
+ if (status.status === 'SUCCEEDED') {
61
+ const output = JSON.parse(status.output ?? '{}');
62
+ cliLogger.info({}, '\n=== Power Tuning Results ===');
63
+ cliLogger.info({ domain, primitive }, 'Power tuning complete');
64
+ if (output.results && Array.isArray(output.results) && output.results.length > 0) {
65
+ const optimal = output.results.reduce((best, r) => r.cost < best.cost ? r : best);
66
+ const baseline = output.results[0];
67
+ const costPercentChange = ((optimal.cost / baseline.cost - 1) * 100).toFixed(1);
68
+ const durationPercentChange = ((optimal.duration / baseline.duration - 1) * 100).toFixed(1);
69
+ cliLogger.info({ memory: optimal.power }, `Optimal memory size: ${optimal.power} MB`);
70
+ cliLogger.info({ change: costPercentChange }, `Expected cost change: ${costPercentChange}%`);
71
+ cliLogger.info({ change: durationPercentChange }, `Expected duration change: ${durationPercentChange}%`);
72
+ cliLogger.info({}, `To apply: update your domain registry config:`);
73
+ cliLogger.info({}, ` domains/${domain}/domain.config.ts → defaultDeployment.memory = ${optimal.power}`);
74
+ }
75
+ cliLogger.info({}, `Visualization: https://lambda-power-tuning.show/#${Buffer.from(JSON.stringify(output)).toString('base64')}`);
76
+ return;
77
+ }
78
+ cliLogger.error({ status }, `Execution failed with status: ${status.status}`);
79
+ if (status.cause) {
80
+ cliLogger.error({}, `Cause: ${status.cause}`);
81
+ }
82
+ process.exit(1);
83
+ }
84
+ cliLogger.error({}, 'Timeout: execution did not complete within 5 minutes.');
85
+ cliLogger.error({ executionArn }, `Check manually: aws stepfunctions describe-execution --execution-arn ${executionArn}`);
86
+ process.exit(1);
87
+ }
@@ -0,0 +1,6 @@
1
+ export interface ReseedPageOptions {
2
+ pageName?: string;
3
+ all?: boolean;
4
+ projectDir?: string;
5
+ }
6
+ export declare function runReseedPage(opts: ReseedPageOptions): Promise<void>;
@@ -0,0 +1,297 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join, resolve, dirname } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ import { createGunzip } from 'node:zlib';
5
+ import { Readable } from 'node:stream';
6
+ import { cliLogger } from '../utils/logger.js';
7
+ import { fetchVersionsJson, fetchModulesJson, fetchModuleTarball, } from '../utils/s3-fetch.js';
8
+ import { readManifest } from '../utils/manifest.js';
9
+ import { readScaffoldConfig } from '../utils/scaffold-config.js';
10
+ const SCAFFOLD_BUCKET = 'mc-scaffold';
11
+ // ── Seed page categories ───────────────────────────────────────────────────────
12
+ const SEED_PAGE_CATEGORIES = ['auth', 'account', 'shell', '_errors'];
13
+ const SEED_LAYOUT_DIR = 'layouts';
14
+ // ── Tarball parsing ────────────────────────────────────────────────────────────
15
+ async function parseTarball(buf) {
16
+ const decompressed = await new Promise((res, rej) => {
17
+ const gunzip = createGunzip();
18
+ const chunks = [];
19
+ const input = Readable.from(buf);
20
+ input.pipe(gunzip);
21
+ gunzip.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
22
+ gunzip.on('end', () => res(Buffer.concat(chunks)));
23
+ gunzip.on('error', rej);
24
+ });
25
+ const entries = [];
26
+ let offset = 0;
27
+ while (offset + 512 <= decompressed.length) {
28
+ const header = decompressed.slice(offset, offset + 512);
29
+ if (header.every((b) => b === 0))
30
+ break;
31
+ let name = header.slice(0, 100).toString('utf-8').replace(/\0.*/, '');
32
+ const prefix = header.slice(345, 500).toString('utf-8').replace(/\0.*/, '');
33
+ if (prefix)
34
+ name = `${prefix}/${name}`;
35
+ const sizeStr = header.slice(124, 136).toString('utf-8').replace(/\0.*/, '').trim();
36
+ const size = parseInt(sizeStr, 8) || 0;
37
+ const typeFlag = header[156];
38
+ const isRegularFile = typeFlag === 0x30 || typeFlag === 0 || typeFlag === undefined;
39
+ offset += 512;
40
+ if (isRegularFile && name && size > 0) {
41
+ const content = decompressed.slice(offset, offset + size);
42
+ entries.push({ path: name, content });
43
+ }
44
+ offset += Math.ceil(size / 512) * 512;
45
+ }
46
+ return entries;
47
+ }
48
+ function getTarEntry(entries, targetPath) {
49
+ return entries.find((e) => {
50
+ const normalized = e.path.replace(/^\.\//, '').replace(/^[^/]+\//, '');
51
+ return normalized === targetPath || e.path === targetPath || e.path.endsWith(`/${targetPath}`);
52
+ });
53
+ }
54
+ // ── GitHub helpers ─────────────────────────────────────────────────────────────
55
+ function detectGitRemote(projectDir) {
56
+ try {
57
+ const url = execSync('git remote get-url origin', {
58
+ cwd: projectDir,
59
+ encoding: 'utf8',
60
+ stdio: ['pipe', 'pipe', 'pipe'],
61
+ }).trim();
62
+ const sshMatch = url.match(/git@github\.com:([^/]+)\/([^.]+?)(?:\.git)?$/);
63
+ if (sshMatch)
64
+ return { owner: sshMatch[1], repo: sshMatch[2] };
65
+ const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/)?$/);
66
+ if (httpsMatch)
67
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
68
+ return null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ async function createGitHubPR(owner, repo, token, branch, title, body, labels) {
75
+ const url = `https://api.github.com/repos/${owner}/${repo}/pulls`;
76
+ const res = await fetch(url, {
77
+ method: 'POST',
78
+ headers: {
79
+ Authorization: `Bearer ${token}`,
80
+ Accept: 'application/vnd.github+json',
81
+ 'Content-Type': 'application/json',
82
+ 'X-GitHub-Api-Version': '2022-11-28',
83
+ },
84
+ body: JSON.stringify({
85
+ title,
86
+ body,
87
+ head: branch,
88
+ base: 'develop',
89
+ labels,
90
+ }),
91
+ });
92
+ if (!res.ok) {
93
+ const err = await res.text();
94
+ throw new Error(`GitHub PR creation failed (${res.status}): ${err}`);
95
+ }
96
+ const data = (await res.json());
97
+ return { url: data.html_url, number: data.number };
98
+ }
99
+ // ── Seed page fetching ─────────────────────────────────────────────────────────
100
+ async function fetchSeedPagesToReseed(tarEntries, tarManifest, pageName) {
101
+ const results = [];
102
+ if (pageName) {
103
+ // Single page mode: fetch one specific page
104
+ for (const fileEntry of tarManifest) {
105
+ if (fileEntry.path.includes(`/pages/`) && fileEntry.path.includes(`${pageName}.tsx`)) {
106
+ const tarEntry = getTarEntry(tarEntries, fileEntry.path);
107
+ if (tarEntry) {
108
+ let installPath = fileEntry.installedAs ?? fileEntry.path;
109
+ if (installPath.endsWith('.tpl'))
110
+ installPath = installPath.slice(0, -4);
111
+ results.push({
112
+ path: installPath,
113
+ content: tarEntry.content.toString('utf-8'),
114
+ });
115
+ }
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ // Bulk mode (--all): fetch all seed pages
121
+ for (const fileEntry of tarManifest) {
122
+ const isSeedPage = isSeedPagePath(fileEntry.path);
123
+ if (isSeedPage) {
124
+ const tarEntry = getTarEntry(tarEntries, fileEntry.path);
125
+ if (tarEntry) {
126
+ let installPath = fileEntry.installedAs ?? fileEntry.path;
127
+ if (installPath.endsWith('.tpl'))
128
+ installPath = installPath.slice(0, -4);
129
+ results.push({
130
+ path: installPath,
131
+ content: tarEntry.content.toString('utf-8'),
132
+ });
133
+ }
134
+ }
135
+ }
136
+ }
137
+ return results;
138
+ }
139
+ function isSeedPagePath(filePath) {
140
+ const seedPatterns = [
141
+ /^frontend\/src\/pages\/auth\//,
142
+ /^frontend\/src\/pages\/account\//,
143
+ /^frontend\/src\/pages\/shell\//,
144
+ /^frontend\/src\/pages\/_errors\//,
145
+ /^frontend\/src\/layouts\//,
146
+ ];
147
+ return seedPatterns.some((pattern) => pattern.test(filePath));
148
+ }
149
+ // ── Main export ────────────────────────────────────────────────────────────────
150
+ export async function runReseedPage(opts) {
151
+ const projectDir = resolve(opts.projectDir ?? process.cwd());
152
+ if (!opts.pageName && !opts.all) {
153
+ throw new Error('Provide a page name or use --all to reseed all seed pages');
154
+ }
155
+ const mode = opts.all ? 'bulk' : 'single';
156
+ console.log(`\nTIB Reseed Page (${mode} mode)\n`);
157
+ console.log(`Project: ${projectDir}\n`);
158
+ // 1. Read manifest + scaffold-config
159
+ const manifest = await readManifest(projectDir);
160
+ if (!manifest) {
161
+ throw new Error(`.mc/manifest.json not found in ${projectDir}. Is this a TIB scaffold project?`);
162
+ }
163
+ const scaffoldConfig = await readScaffoldConfig(projectDir);
164
+ const bucket = scaffoldConfig.scaffoldBucket ?? SCAFFOLD_BUCKET;
165
+ // 2. Get latest version
166
+ const versionsJson = await fetchVersionsJson(bucket);
167
+ const latestVersion = versionsJson.latest;
168
+ console.log(`Current version : ${manifest.scaffoldVersion}`);
169
+ console.log(`Latest version : ${latestVersion}\n`);
170
+ // 3. Fetch frontend module tarball
171
+ const modulesJson = await fetchModulesJson(bucket, latestVersion);
172
+ const frontendModule = modulesJson.modules.find((m) => m.id === 'frontend');
173
+ if (!frontendModule) {
174
+ throw new Error(`Frontend module not found in modules.json for version ${latestVersion}`);
175
+ }
176
+ console.log(`Fetching frontend module ${frontendModule.id}@${frontendModule.version}...\n`);
177
+ const tarBuf = await fetchModuleTarball(bucket, latestVersion, frontendModule.tarball, frontendModule.sha256);
178
+ const tarEntries = await parseTarball(tarBuf);
179
+ const manifestEntry = getTarEntry(tarEntries, 'MANIFEST.json');
180
+ if (!manifestEntry) {
181
+ throw new Error('No MANIFEST.json in frontend module tarball');
182
+ }
183
+ const tarManifest = JSON.parse(manifestEntry.content.toString('utf-8'));
184
+ // 4. Fetch seed pages to reseed
185
+ const seedPages = await fetchSeedPagesToReseed(tarEntries, tarManifest, opts.pageName);
186
+ if (seedPages.length === 0) {
187
+ const msg = opts.pageName
188
+ ? `No seed page found matching "${opts.pageName}"`
189
+ : 'No seed pages found to reseed';
190
+ throw new Error(msg);
191
+ }
192
+ console.log(`Found ${seedPages.length} seed page(s) to reseed`);
193
+ seedPages.forEach((p) => {
194
+ console.log(` - ${p.path}`);
195
+ });
196
+ console.log('');
197
+ // 5. Create git branch for the reseed
198
+ const branchName = opts.all
199
+ ? `tib-reseed/all-v${latestVersion}`
200
+ : `tib-reseed/${opts.pageName}-v${latestVersion}`;
201
+ try {
202
+ execSync(`git -C "${projectDir}" checkout -b "${branchName}"`, {
203
+ stdio: ['pipe', 'pipe', 'pipe'],
204
+ });
205
+ }
206
+ catch {
207
+ // Branch may already exist
208
+ try {
209
+ execSync(`git -C "${projectDir}" checkout "${branchName}"`, {
210
+ stdio: ['pipe', 'pipe', 'pipe'],
211
+ });
212
+ }
213
+ catch {
214
+ throw new Error(`Failed to create or checkout branch: ${branchName}`);
215
+ }
216
+ }
217
+ // 6. Write seed pages to disk
218
+ for (const seedPage of seedPages) {
219
+ const diskPath = join(projectDir, seedPage.path);
220
+ console.log(`Writing ${seedPage.path}...`);
221
+ await mkdir(dirname(diskPath), { recursive: true });
222
+ await writeFile(diskPath, seedPage.content, 'utf-8');
223
+ }
224
+ // 7. Commit changes
225
+ execSync(`git -C "${projectDir}" add -A`, { stdio: 'pipe' });
226
+ let hasChanges = false;
227
+ try {
228
+ const status = execSync(`git -C "${projectDir}" status --porcelain`, {
229
+ encoding: 'utf8',
230
+ stdio: ['pipe', 'pipe', 'pipe'],
231
+ }).trim();
232
+ hasChanges = status.length > 0;
233
+ }
234
+ catch {
235
+ // ignore
236
+ }
237
+ if (!hasChanges) {
238
+ console.log('No changes to commit — seed pages are already up to date.\n');
239
+ return;
240
+ }
241
+ const commitMsg = opts.all
242
+ ? `chore: [DESTRUCTIVE] bulk reseed all seed pages to v${latestVersion}`
243
+ : `chore: [DESTRUCTIVE] reseed ${opts.pageName} to v${latestVersion}`;
244
+ execSync(`git -C "${projectDir}" commit -m "${commitMsg}"`, { stdio: 'pipe' });
245
+ try {
246
+ execSync(`git -C "${projectDir}" push --set-upstream origin "${branchName}"`, {
247
+ stdio: 'pipe',
248
+ });
249
+ }
250
+ catch {
251
+ cliLogger.warn({ branchName }, 'reseed-page: git push failed — skipping PR creation');
252
+ console.log(`\nCommit created on branch: ${branchName}`);
253
+ console.log('Note: push failed — create the PR manually.\n');
254
+ return;
255
+ }
256
+ // 8. Create GitHub PR
257
+ const token = process.env['GITHUB_TOKEN'];
258
+ const remote = detectGitRemote(projectDir);
259
+ if (!token) {
260
+ console.log(`\nBranch pushed: ${branchName}`);
261
+ console.log('Set GITHUB_TOKEN env var to create a PR automatically.\n');
262
+ return;
263
+ }
264
+ if (!remote) {
265
+ console.log(`\nBranch pushed: ${branchName}`);
266
+ console.log('Could not detect GitHub owner/repo from git remote — create the PR manually.\n');
267
+ return;
268
+ }
269
+ const prTitle = opts.all
270
+ ? `[DESTRUCTIVE] Bulk reseed all seed pages to scaffold v${latestVersion}`
271
+ : `[DESTRUCTIVE] Reseed ${opts.pageName} to scaffold v${latestVersion}`;
272
+ const prBody = buildReseedPagePrBody(opts.pageName ?? 'all pages', latestVersion, seedPages);
273
+ const prResult = await createGitHubPR(remote.owner, remote.repo, token, branchName, prTitle, prBody, ['tib-destructive-reseed']);
274
+ console.log(`\nPR created: ${prResult.url}\n`);
275
+ cliLogger.info({ prUrl: prResult.url, latestVersion }, 'reseed-page: PR created');
276
+ }
277
+ function buildReseedPagePrBody(pageName, version, pages) {
278
+ const lines = [
279
+ '## ⚠️ DESTRUCTIVE: Reseed Seed Pages',
280
+ '',
281
+ 'This PR **overwrites** the following seed page(s) with the latest scaffold version.',
282
+ 'This is a destructive operation — verify changes carefully.',
283
+ '',
284
+ '### Seed pages to be overwritten',
285
+ '',
286
+ ];
287
+ for (const page of pages) {
288
+ lines.push(`- \`${page.path}\``);
289
+ }
290
+ lines.push('');
291
+ lines.push('### What to do');
292
+ lines.push('1. Review the file diffs in this PR');
293
+ lines.push('2. If acceptable, merge this PR');
294
+ lines.push('3. The seed pages will be reset to their scaffold defaults');
295
+ lines.push('');
296
+ return lines.join('\n');
297
+ }
@@ -0,0 +1,15 @@
1
+ export interface ShowDnsOptions {
2
+ env?: string;
3
+ projectDir?: string;
4
+ }
5
+ /**
6
+ * tib show-dns [--env dev|staging|prod] [--project-dir <path>]
7
+ *
8
+ * Reads the scaffold-config.json and prints DNS records that must be added for
9
+ * custom domain wiring: certificate validation CNAMEs (step 1) and traffic
10
+ * routing CNAMEs (step 2).
11
+ *
12
+ * For the routing CNAME the CloudFront domain is read from the CDK outputs file
13
+ * (.mc/cdk-outputs.json) if available; otherwise it is shown as a placeholder.
14
+ */
15
+ export declare function runShowDns(opts: ShowDnsOptions): Promise<void>;