@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,410 @@
|
|
|
1
|
+
import { mkdir, writeFile, unlink, access } 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 {
|
|
8
|
+
fetchVersionsJson,
|
|
9
|
+
fetchModulesJson,
|
|
10
|
+
fetchModuleTarball,
|
|
11
|
+
type S3ModuleEntry,
|
|
12
|
+
} from '../utils/s3-fetch.js';
|
|
13
|
+
import { computeChecksumString } from '../utils/checksum.js';
|
|
14
|
+
import { injectHeader } from '../utils/header-inject.js';
|
|
15
|
+
import { readManifest } from '../utils/manifest.js';
|
|
16
|
+
import { readScaffoldConfig } from '../utils/scaffold-config.js';
|
|
17
|
+
|
|
18
|
+
const SCAFFOLD_BUCKET = 'mc-scaffold';
|
|
19
|
+
|
|
20
|
+
// ── Tarball types ──────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
interface TarEntry {
|
|
23
|
+
path: string;
|
|
24
|
+
content: Buffer;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface TarballManifestEntry {
|
|
28
|
+
path: string;
|
|
29
|
+
sha256: string;
|
|
30
|
+
isTemplate: boolean;
|
|
31
|
+
installedAs?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Seed page categories ───────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const SEED_PAGE_CATEGORIES = ['auth', 'account', 'shell', '_errors'];
|
|
37
|
+
const SEED_LAYOUT_DIR = 'layouts';
|
|
38
|
+
|
|
39
|
+
export interface ReseedPageOptions {
|
|
40
|
+
pageName?: string;
|
|
41
|
+
all?: boolean;
|
|
42
|
+
projectDir?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Tarball parsing ────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
async function parseTarball(buf: Buffer): Promise<TarEntry[]> {
|
|
48
|
+
const decompressed = await new Promise<Buffer>((res, rej) => {
|
|
49
|
+
const gunzip = createGunzip();
|
|
50
|
+
const chunks: Buffer[] = [];
|
|
51
|
+
const input = Readable.from(buf);
|
|
52
|
+
input.pipe(gunzip);
|
|
53
|
+
gunzip.on('data', (chunk: Buffer) =>
|
|
54
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as ArrayBuffer))
|
|
55
|
+
);
|
|
56
|
+
gunzip.on('end', () => res(Buffer.concat(chunks)));
|
|
57
|
+
gunzip.on('error', rej);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const entries: TarEntry[] = [];
|
|
61
|
+
let offset = 0;
|
|
62
|
+
|
|
63
|
+
while (offset + 512 <= decompressed.length) {
|
|
64
|
+
const header = decompressed.slice(offset, offset + 512);
|
|
65
|
+
if (header.every((b) => b === 0)) break;
|
|
66
|
+
|
|
67
|
+
let name = header.slice(0, 100).toString('utf-8').replace(/\0.*/, '');
|
|
68
|
+
const prefix = header.slice(345, 500).toString('utf-8').replace(/\0.*/, '');
|
|
69
|
+
if (prefix) name = `${prefix}/${name}`;
|
|
70
|
+
|
|
71
|
+
const sizeStr = header.slice(124, 136).toString('utf-8').replace(/\0.*/, '').trim();
|
|
72
|
+
const size = parseInt(sizeStr, 8) || 0;
|
|
73
|
+
const typeFlag = header[156];
|
|
74
|
+
const isRegularFile = typeFlag === 0x30 || typeFlag === 0 || typeFlag === undefined;
|
|
75
|
+
|
|
76
|
+
offset += 512;
|
|
77
|
+
|
|
78
|
+
if (isRegularFile && name && size > 0) {
|
|
79
|
+
const content = decompressed.slice(offset, offset + size);
|
|
80
|
+
entries.push({ path: name, content });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
offset += Math.ceil(size / 512) * 512;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return entries;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getTarEntry(entries: TarEntry[], targetPath: string): TarEntry | undefined {
|
|
90
|
+
return entries.find((e) => {
|
|
91
|
+
const normalized = e.path.replace(/^\.\//, '').replace(/^[^/]+\//, '');
|
|
92
|
+
return normalized === targetPath || e.path === targetPath || e.path.endsWith(`/${targetPath}`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── GitHub helpers ─────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function detectGitRemote(projectDir: string): { owner: string; repo: string } | null {
|
|
99
|
+
try {
|
|
100
|
+
const url = execSync('git remote get-url origin', {
|
|
101
|
+
cwd: projectDir,
|
|
102
|
+
encoding: 'utf8',
|
|
103
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
104
|
+
}).trim();
|
|
105
|
+
|
|
106
|
+
const sshMatch = url.match(/git@github\.com:([^/]+)\/([^.]+?)(?:\.git)?$/);
|
|
107
|
+
if (sshMatch) return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
108
|
+
|
|
109
|
+
const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/)?$/);
|
|
110
|
+
if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function createGitHubPR(
|
|
119
|
+
owner: string,
|
|
120
|
+
repo: string,
|
|
121
|
+
token: string,
|
|
122
|
+
branch: string,
|
|
123
|
+
title: string,
|
|
124
|
+
body: string,
|
|
125
|
+
labels?: string[]
|
|
126
|
+
): Promise<{ url: string; number: number }> {
|
|
127
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/pulls`;
|
|
128
|
+
const res = await fetch(url, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: `Bearer ${token}`,
|
|
132
|
+
Accept: 'application/vnd.github+json',
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
title,
|
|
138
|
+
body,
|
|
139
|
+
head: branch,
|
|
140
|
+
base: 'develop',
|
|
141
|
+
labels,
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!res.ok) {
|
|
146
|
+
const err = await res.text();
|
|
147
|
+
throw new Error(`GitHub PR creation failed (${res.status}): ${err}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const data = (await res.json()) as { html_url: string; number: number };
|
|
151
|
+
return { url: data.html_url, number: data.number };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Seed page fetching ─────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
async function fetchSeedPagesToReseed(
|
|
157
|
+
tarEntries: TarEntry[],
|
|
158
|
+
tarManifest: TarballManifestEntry[],
|
|
159
|
+
pageName?: string
|
|
160
|
+
): Promise<Array<{ path: string; content: string }>> {
|
|
161
|
+
const results: Array<{ path: string; content: string }> = [];
|
|
162
|
+
|
|
163
|
+
if (pageName) {
|
|
164
|
+
// Single page mode: fetch one specific page
|
|
165
|
+
for (const fileEntry of tarManifest) {
|
|
166
|
+
if (fileEntry.path.includes(`/pages/`) && fileEntry.path.includes(`${pageName}.tsx`)) {
|
|
167
|
+
const tarEntry = getTarEntry(tarEntries, fileEntry.path);
|
|
168
|
+
if (tarEntry) {
|
|
169
|
+
let installPath = fileEntry.installedAs ?? fileEntry.path;
|
|
170
|
+
if (installPath.endsWith('.tpl')) installPath = installPath.slice(0, -4);
|
|
171
|
+
results.push({
|
|
172
|
+
path: installPath,
|
|
173
|
+
content: tarEntry.content.toString('utf-8'),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
// Bulk mode (--all): fetch all seed pages
|
|
180
|
+
for (const fileEntry of tarManifest) {
|
|
181
|
+
const isSeedPage = isSeedPagePath(fileEntry.path);
|
|
182
|
+
if (isSeedPage) {
|
|
183
|
+
const tarEntry = getTarEntry(tarEntries, fileEntry.path);
|
|
184
|
+
if (tarEntry) {
|
|
185
|
+
let installPath = fileEntry.installedAs ?? fileEntry.path;
|
|
186
|
+
if (installPath.endsWith('.tpl')) installPath = installPath.slice(0, -4);
|
|
187
|
+
results.push({
|
|
188
|
+
path: installPath,
|
|
189
|
+
content: tarEntry.content.toString('utf-8'),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return results;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isSeedPagePath(filePath: string): boolean {
|
|
200
|
+
const seedPatterns = [
|
|
201
|
+
/^frontend\/src\/pages\/auth\//,
|
|
202
|
+
/^frontend\/src\/pages\/account\//,
|
|
203
|
+
/^frontend\/src\/pages\/shell\//,
|
|
204
|
+
/^frontend\/src\/pages\/_errors\//,
|
|
205
|
+
/^frontend\/src\/layouts\//,
|
|
206
|
+
];
|
|
207
|
+
return seedPatterns.some((pattern) => pattern.test(filePath));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Main export ────────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
export async function runReseedPage(opts: ReseedPageOptions): Promise<void> {
|
|
213
|
+
const projectDir = resolve(opts.projectDir ?? process.cwd());
|
|
214
|
+
|
|
215
|
+
if (!opts.pageName && !opts.all) {
|
|
216
|
+
throw new Error('Provide a page name or use --all to reseed all seed pages');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const mode = opts.all ? 'bulk' : 'single';
|
|
220
|
+
console.log(`\nTIB Reseed Page (${mode} mode)\n`);
|
|
221
|
+
console.log(`Project: ${projectDir}\n`);
|
|
222
|
+
|
|
223
|
+
// 1. Read manifest + scaffold-config
|
|
224
|
+
const manifest = await readManifest(projectDir);
|
|
225
|
+
if (!manifest) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`.mc/manifest.json not found in ${projectDir}. Is this a TIB scaffold project?`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const scaffoldConfig = await readScaffoldConfig(projectDir);
|
|
232
|
+
const bucket = scaffoldConfig.scaffoldBucket ?? SCAFFOLD_BUCKET;
|
|
233
|
+
|
|
234
|
+
// 2. Get latest version
|
|
235
|
+
const versionsJson = await fetchVersionsJson(bucket);
|
|
236
|
+
const latestVersion = versionsJson.latest;
|
|
237
|
+
|
|
238
|
+
console.log(`Current version : ${manifest.scaffoldVersion}`);
|
|
239
|
+
console.log(`Latest version : ${latestVersion}\n`);
|
|
240
|
+
|
|
241
|
+
// 3. Fetch frontend module tarball
|
|
242
|
+
const modulesJson = await fetchModulesJson(bucket, latestVersion);
|
|
243
|
+
const frontendModule = modulesJson.modules.find((m) => m.id === 'frontend');
|
|
244
|
+
if (!frontendModule) {
|
|
245
|
+
throw new Error(`Frontend module not found in modules.json for version ${latestVersion}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log(`Fetching frontend module ${frontendModule.id}@${frontendModule.version}...\n`);
|
|
249
|
+
|
|
250
|
+
const tarBuf = await fetchModuleTarball(
|
|
251
|
+
bucket,
|
|
252
|
+
latestVersion,
|
|
253
|
+
frontendModule.tarball,
|
|
254
|
+
frontendModule.sha256
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const tarEntries = await parseTarball(tarBuf);
|
|
258
|
+
const manifestEntry = getTarEntry(tarEntries, 'MANIFEST.json');
|
|
259
|
+
if (!manifestEntry) {
|
|
260
|
+
throw new Error('No MANIFEST.json in frontend module tarball');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const tarManifest = JSON.parse(
|
|
264
|
+
manifestEntry.content.toString('utf-8')
|
|
265
|
+
) as TarballManifestEntry[];
|
|
266
|
+
|
|
267
|
+
// 4. Fetch seed pages to reseed
|
|
268
|
+
const seedPages = await fetchSeedPagesToReseed(tarEntries, tarManifest, opts.pageName);
|
|
269
|
+
|
|
270
|
+
if (seedPages.length === 0) {
|
|
271
|
+
const msg = opts.pageName
|
|
272
|
+
? `No seed page found matching "${opts.pageName}"`
|
|
273
|
+
: 'No seed pages found to reseed';
|
|
274
|
+
throw new Error(msg);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(`Found ${seedPages.length} seed page(s) to reseed`);
|
|
278
|
+
seedPages.forEach((p) => {
|
|
279
|
+
console.log(` - ${p.path}`);
|
|
280
|
+
});
|
|
281
|
+
console.log('');
|
|
282
|
+
|
|
283
|
+
// 5. Create git branch for the reseed
|
|
284
|
+
const branchName = opts.all
|
|
285
|
+
? `tib-reseed/all-v${latestVersion}`
|
|
286
|
+
: `tib-reseed/${opts.pageName}-v${latestVersion}`;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
execSync(`git -C "${projectDir}" checkout -b "${branchName}"`, {
|
|
290
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
291
|
+
});
|
|
292
|
+
} catch {
|
|
293
|
+
// Branch may already exist
|
|
294
|
+
try {
|
|
295
|
+
execSync(`git -C "${projectDir}" checkout "${branchName}"`, {
|
|
296
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
297
|
+
});
|
|
298
|
+
} catch {
|
|
299
|
+
throw new Error(`Failed to create or checkout branch: ${branchName}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 6. Write seed pages to disk
|
|
304
|
+
for (const seedPage of seedPages) {
|
|
305
|
+
const diskPath = join(projectDir, seedPage.path);
|
|
306
|
+
console.log(`Writing ${seedPage.path}...`);
|
|
307
|
+
await mkdir(dirname(diskPath), { recursive: true });
|
|
308
|
+
await writeFile(diskPath, seedPage.content, 'utf-8');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 7. Commit changes
|
|
312
|
+
execSync(`git -C "${projectDir}" add -A`, { stdio: 'pipe' });
|
|
313
|
+
|
|
314
|
+
let hasChanges = false;
|
|
315
|
+
try {
|
|
316
|
+
const status = execSync(`git -C "${projectDir}" status --porcelain`, {
|
|
317
|
+
encoding: 'utf8',
|
|
318
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
319
|
+
}).trim();
|
|
320
|
+
hasChanges = status.length > 0;
|
|
321
|
+
} catch {
|
|
322
|
+
// ignore
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!hasChanges) {
|
|
326
|
+
console.log('No changes to commit — seed pages are already up to date.\n');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const commitMsg = opts.all
|
|
331
|
+
? `chore: [DESTRUCTIVE] bulk reseed all seed pages to v${latestVersion}`
|
|
332
|
+
: `chore: [DESTRUCTIVE] reseed ${opts.pageName} to v${latestVersion}`;
|
|
333
|
+
|
|
334
|
+
execSync(`git -C "${projectDir}" commit -m "${commitMsg}"`, { stdio: 'pipe' });
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
execSync(`git -C "${projectDir}" push --set-upstream origin "${branchName}"`, {
|
|
338
|
+
stdio: 'pipe',
|
|
339
|
+
});
|
|
340
|
+
} catch {
|
|
341
|
+
cliLogger.warn({ branchName }, 'reseed-page: git push failed — skipping PR creation');
|
|
342
|
+
console.log(`\nCommit created on branch: ${branchName}`);
|
|
343
|
+
console.log('Note: push failed — create the PR manually.\n');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 8. Create GitHub PR
|
|
348
|
+
const token = process.env['GITHUB_TOKEN'];
|
|
349
|
+
const remote = detectGitRemote(projectDir);
|
|
350
|
+
|
|
351
|
+
if (!token) {
|
|
352
|
+
console.log(`\nBranch pushed: ${branchName}`);
|
|
353
|
+
console.log('Set GITHUB_TOKEN env var to create a PR automatically.\n');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!remote) {
|
|
358
|
+
console.log(`\nBranch pushed: ${branchName}`);
|
|
359
|
+
console.log('Could not detect GitHub owner/repo from git remote — create the PR manually.\n');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const prTitle = opts.all
|
|
364
|
+
? `[DESTRUCTIVE] Bulk reseed all seed pages to scaffold v${latestVersion}`
|
|
365
|
+
: `[DESTRUCTIVE] Reseed ${opts.pageName} to scaffold v${latestVersion}`;
|
|
366
|
+
|
|
367
|
+
const prBody = buildReseedPagePrBody(
|
|
368
|
+
opts.pageName ?? 'all pages',
|
|
369
|
+
latestVersion,
|
|
370
|
+
seedPages
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const prResult = await createGitHubPR(
|
|
374
|
+
remote.owner,
|
|
375
|
+
remote.repo,
|
|
376
|
+
token,
|
|
377
|
+
branchName,
|
|
378
|
+
prTitle,
|
|
379
|
+
prBody,
|
|
380
|
+
['tib-destructive-reseed']
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
console.log(`\nPR created: ${prResult.url}\n`);
|
|
384
|
+
cliLogger.info({ prUrl: prResult.url, latestVersion }, 'reseed-page: PR created');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function buildReseedPagePrBody(pageName: string, version: string, pages: Array<{ path: string; content: string }>): string {
|
|
388
|
+
const lines: string[] = [
|
|
389
|
+
'## ⚠️ DESTRUCTIVE: Reseed Seed Pages',
|
|
390
|
+
'',
|
|
391
|
+
'This PR **overwrites** the following seed page(s) with the latest scaffold version.',
|
|
392
|
+
'This is a destructive operation — verify changes carefully.',
|
|
393
|
+
'',
|
|
394
|
+
'### Seed pages to be overwritten',
|
|
395
|
+
'',
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
for (const page of pages) {
|
|
399
|
+
lines.push(`- \`${page.path}\``);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
lines.push('');
|
|
403
|
+
lines.push('### What to do');
|
|
404
|
+
lines.push('1. Review the file diffs in this PR');
|
|
405
|
+
lines.push('2. If acceptable, merge this PR');
|
|
406
|
+
lines.push('3. The seed pages will be reset to their scaffold defaults');
|
|
407
|
+
lines.push('');
|
|
408
|
+
|
|
409
|
+
return lines.join('\n');
|
|
410
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { cliLogger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export interface ShowDnsOptions {
|
|
6
|
+
env?: string;
|
|
7
|
+
projectDir?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface DnsRecord {
|
|
11
|
+
type: string;
|
|
12
|
+
name: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface EnvDnsInfo {
|
|
17
|
+
dashboardUrl?: string;
|
|
18
|
+
status: string;
|
|
19
|
+
validationRecords: DnsRecord[];
|
|
20
|
+
routingRecords: DnsRecord[];
|
|
21
|
+
errorMessage?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toZoneLine(r: DnsRecord): string {
|
|
25
|
+
return `${r.name}. 300 IN ${r.type} ${r.value}.`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* tib show-dns [--env dev|staging|prod] [--project-dir <path>]
|
|
30
|
+
*
|
|
31
|
+
* Reads the scaffold-config.json and prints DNS records that must be added for
|
|
32
|
+
* custom domain wiring: certificate validation CNAMEs (step 1) and traffic
|
|
33
|
+
* routing CNAMEs (step 2).
|
|
34
|
+
*
|
|
35
|
+
* For the routing CNAME the CloudFront domain is read from the CDK outputs file
|
|
36
|
+
* (.mc/cdk-outputs.json) if available; otherwise it is shown as a placeholder.
|
|
37
|
+
*/
|
|
38
|
+
export async function runShowDns(opts: ShowDnsOptions): Promise<void> {
|
|
39
|
+
const projectDir = opts.projectDir ? path.resolve(opts.projectDir) : process.cwd();
|
|
40
|
+
const configPath = path.join(projectDir, '.tib', 'scaffold-config.json');
|
|
41
|
+
|
|
42
|
+
let config: { environments?: Record<string, { dashboardUrl?: string }> };
|
|
43
|
+
try {
|
|
44
|
+
const raw = await readFile(configPath, 'utf-8');
|
|
45
|
+
config = JSON.parse(raw) as typeof config;
|
|
46
|
+
} catch {
|
|
47
|
+
console.error('[tib] Could not read .mc/scaffold-config.json — run from a TIB project root.');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const environments = config.environments ?? {};
|
|
52
|
+
if (Object.keys(environments).length === 0) {
|
|
53
|
+
console.log('[tib] No environments configured. Set a custom domain via `tib show-dns` or the Setup tab.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Try to load CDK outputs for routing records
|
|
58
|
+
let cdkOutputs: Record<string, Record<string, string>> = {};
|
|
59
|
+
try {
|
|
60
|
+
const outputsPath = path.join(projectDir, '.tib', 'cdk-outputs.json');
|
|
61
|
+
const raw = await readFile(outputsPath, 'utf-8');
|
|
62
|
+
cdkOutputs = JSON.parse(raw) as typeof cdkOutputs;
|
|
63
|
+
} catch {
|
|
64
|
+
// No CDK outputs yet — routing CNAMEs will show a placeholder
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const targetEnvs = opts.env
|
|
68
|
+
? [opts.env]
|
|
69
|
+
: Object.keys(environments);
|
|
70
|
+
|
|
71
|
+
for (const env of targetEnvs) {
|
|
72
|
+
const cfg = environments[env];
|
|
73
|
+
if (!cfg?.dashboardUrl) {
|
|
74
|
+
console.log(`\n;; ${env}: no custom domain configured`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const info = await buildEnvDnsInfo(env, cfg.dashboardUrl, cdkOutputs);
|
|
79
|
+
printEnvDns(env, info);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function buildEnvDnsInfo(
|
|
84
|
+
env: string,
|
|
85
|
+
dashboardUrl: string,
|
|
86
|
+
cdkOutputs: Record<string, Record<string, string>>
|
|
87
|
+
): Promise<EnvDnsInfo> {
|
|
88
|
+
// Look for cert validation records from CDK outputs
|
|
89
|
+
// Cert validation CNAMEs are emitted by ACM and captured in CDK outputs under the cert resource
|
|
90
|
+
const certArnOutputKey = `tib-cert-${env}-arn`;
|
|
91
|
+
const certArn = Object.values(cdkOutputs)
|
|
92
|
+
.flatMap(stack => Object.entries(stack))
|
|
93
|
+
.find(([k]) => k === certArnOutputKey)?.[1];
|
|
94
|
+
|
|
95
|
+
// Look for the CloudFront distribution domain from CDK outputs
|
|
96
|
+
const distributionDomainName = Object.values(cdkOutputs)
|
|
97
|
+
.flatMap(stack => Object.entries(stack))
|
|
98
|
+
.find(([k]) => k.toLowerCase().includes('distributiondomain') && k.toLowerCase().includes(env))?.[1]
|
|
99
|
+
?? `<run cdk synth — distribution domain not yet available>`;
|
|
100
|
+
|
|
101
|
+
const validationRecords: DnsRecord[] = certArn
|
|
102
|
+
// When CDK outputs include validation record overrides, they'd be here.
|
|
103
|
+
// Without a live describe-certificate call, we surface a placeholder.
|
|
104
|
+
? [{ type: 'CNAME', name: `_acm-validation.${dashboardUrl}`, value: `<see ACM console for cert ${certArn}>` }]
|
|
105
|
+
: [{ type: 'CNAME', name: `_acm-validation.${dashboardUrl}`, value: `<deploy the FrontendCustomDomainStack first>` }];
|
|
106
|
+
|
|
107
|
+
const routingRecords: DnsRecord[] = [
|
|
108
|
+
{ type: 'CNAME', name: dashboardUrl, value: distributionDomainName },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
dashboardUrl,
|
|
113
|
+
status: certArn ? 'deployed' : 'not_deployed',
|
|
114
|
+
validationRecords,
|
|
115
|
+
routingRecords,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function printEnvDns(env: string, info: EnvDnsInfo): void {
|
|
120
|
+
console.log(`\n;; ${env} — ${info.dashboardUrl}`);
|
|
121
|
+
|
|
122
|
+
if (info.validationRecords.length > 0) {
|
|
123
|
+
console.log(';; Certificate validation records (add once, shared across deploys)');
|
|
124
|
+
for (const r of info.validationRecords) {
|
|
125
|
+
console.log(toZoneLine(r));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (info.routingRecords.length > 0) {
|
|
130
|
+
console.log(';; Traffic routing records (update when CloudFront domain changes)');
|
|
131
|
+
for (const r of info.routingRecords) {
|
|
132
|
+
console.log(toZoneLine(r));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (info.errorMessage) {
|
|
137
|
+
cliLogger.error({ env, errorMessage: info.errorMessage }, 'DNS info error');
|
|
138
|
+
}
|
|
139
|
+
}
|