@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,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,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>;
|