@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,367 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import * as tar from 'tar';
|
|
7
|
+
import { cliLogger } from '../utils/logger.js';
|
|
8
|
+
import {
|
|
9
|
+
fetchVersionsJson,
|
|
10
|
+
fetchModulesJson,
|
|
11
|
+
fetchModuleTarball,
|
|
12
|
+
type S3ModuleEntry,
|
|
13
|
+
} from '../utils/s3-fetch.js';
|
|
14
|
+
import { computeChecksumString } from '../utils/checksum.js';
|
|
15
|
+
import { applyTokens, buildTokenContext } from '../utils/token-substitution.js';
|
|
16
|
+
import { injectHeader } from '../utils/header-inject.js';
|
|
17
|
+
import {
|
|
18
|
+
createManifest,
|
|
19
|
+
writeManifest,
|
|
20
|
+
upsertManifestFile,
|
|
21
|
+
type TibManifest,
|
|
22
|
+
} from '../utils/manifest.js';
|
|
23
|
+
import { writeScaffoldConfig, type ScaffoldConfig, type ModuleId, type EnvironmentsConfig } from '../utils/scaffold-config.js';
|
|
24
|
+
|
|
25
|
+
const SCAFFOLD_BUCKET = 'mc-scaffold';
|
|
26
|
+
|
|
27
|
+
export interface CreateProjectOptions {
|
|
28
|
+
name: string;
|
|
29
|
+
outDir?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Prompts ────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
async function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
|
|
35
|
+
return new Promise((res) => rl.question(question, res));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function promptDefault(
|
|
39
|
+
rl: ReturnType<typeof createInterface>,
|
|
40
|
+
question: string,
|
|
41
|
+
defaultVal: string
|
|
42
|
+
): Promise<string> {
|
|
43
|
+
const answer = await prompt(rl, `${question} [${defaultVal}]: `);
|
|
44
|
+
return answer.trim() || defaultVal;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function promptYesNo(
|
|
48
|
+
rl: ReturnType<typeof createInterface>,
|
|
49
|
+
question: string,
|
|
50
|
+
defaultYes = true
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
53
|
+
const answer = await prompt(rl, `${question} [${hint}]: `);
|
|
54
|
+
const trimmed = answer.trim().toLowerCase();
|
|
55
|
+
if (!trimmed) return defaultYes;
|
|
56
|
+
return trimmed === 'y' || trimmed === 'yes';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Topological sort ───────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
function topoSort(modules: S3ModuleEntry[], selected: Set<string>): S3ModuleEntry[] {
|
|
62
|
+
const byId = new Map(modules.map((m) => [m.id, m]));
|
|
63
|
+
const result: S3ModuleEntry[] = [];
|
|
64
|
+
const visited = new Set<string>();
|
|
65
|
+
|
|
66
|
+
function visit(id: string): void {
|
|
67
|
+
if (visited.has(id)) return;
|
|
68
|
+
visited.add(id);
|
|
69
|
+
const mod = byId.get(id);
|
|
70
|
+
if (!mod) return;
|
|
71
|
+
for (const dep of mod.dependencies) {
|
|
72
|
+
if (selected.has(dep)) visit(dep);
|
|
73
|
+
}
|
|
74
|
+
result.push(mod);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Always core first, then backend-lambda, then rest
|
|
78
|
+
const priorityOrder = ['core', 'backend-lambda'];
|
|
79
|
+
for (const id of priorityOrder) {
|
|
80
|
+
if (selected.has(id)) visit(id);
|
|
81
|
+
}
|
|
82
|
+
for (const id of selected) {
|
|
83
|
+
visit(id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Tarball parsing (tar npm package) ─────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
interface TarEntry {
|
|
92
|
+
path: string;
|
|
93
|
+
content: Buffer;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface TarballManifestEntry {
|
|
97
|
+
path: string;
|
|
98
|
+
sha256: string;
|
|
99
|
+
isTemplate: boolean;
|
|
100
|
+
installedAs?: string;
|
|
101
|
+
policy?: 'owned' | 'tracked' | 'seed';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function parseTarball(buf: Buffer): Promise<TarEntry[]> {
|
|
105
|
+
const entries: TarEntry[] = [];
|
|
106
|
+
|
|
107
|
+
await new Promise<void>((resolve, reject) => {
|
|
108
|
+
const parser = new tar.Parser({
|
|
109
|
+
gzip: true,
|
|
110
|
+
onentry(entry: tar.ReadEntry) {
|
|
111
|
+
if (entry.type !== 'File') {
|
|
112
|
+
entry.resume();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const chunks: Buffer[] = [];
|
|
117
|
+
entry.on('data', (chunk: Buffer) => {
|
|
118
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as ArrayBuffer));
|
|
119
|
+
});
|
|
120
|
+
entry.on('end', () => {
|
|
121
|
+
entries.push({ path: entry.path, content: Buffer.concat(chunks) });
|
|
122
|
+
});
|
|
123
|
+
entry.on('error', reject);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
parser.on('finish', resolve);
|
|
128
|
+
parser.on('error', reject);
|
|
129
|
+
|
|
130
|
+
const readable = Readable.from(buf);
|
|
131
|
+
readable.pipe(parser);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return entries;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getTarEntry(entries: TarEntry[], targetPath: string): TarEntry | undefined {
|
|
138
|
+
// Strip leading ./ or component prefix from tarball paths
|
|
139
|
+
return entries.find((e) => {
|
|
140
|
+
const normalized = e.path.replace(/^\.\//, '').replace(/^[^/]+\//, '');
|
|
141
|
+
return normalized === targetPath || e.path === targetPath || e.path.endsWith(`/${targetPath}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Main ───────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
export async function runCreateProject(opts: CreateProjectOptions): Promise<void> {
|
|
148
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// 1. Collect project details
|
|
152
|
+
console.log('\nTIB scaffold — create new project\n');
|
|
153
|
+
|
|
154
|
+
const projectName = await promptDefault(rl, 'Project name', opts.name || 'my-project');
|
|
155
|
+
const awsRegion = await promptDefault(rl, 'AWS region', 'eu-north-1');
|
|
156
|
+
const awsAccountId = await promptDefault(rl, 'AWS account ID', '');
|
|
157
|
+
const includeDatabase = await promptYesNo(rl, 'Include database (Aurora)?', false);
|
|
158
|
+
const includeAuth = await promptYesNo(rl, 'Include authentication (Cognito)?', false);
|
|
159
|
+
const includeFrontend = await promptYesNo(rl, 'Include frontend?', false);
|
|
160
|
+
const includeFlows = await promptYesNo(rl, 'Include flows?', false);
|
|
161
|
+
const adminEmail = includeAuth
|
|
162
|
+
? await promptDefault(rl, 'Admin email', '')
|
|
163
|
+
: undefined;
|
|
164
|
+
|
|
165
|
+
// Optional custom domain for prod (bare domain, no scheme/path/port)
|
|
166
|
+
const BARE_DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/;
|
|
167
|
+
let prodUrl = '';
|
|
168
|
+
if (includeFrontend) {
|
|
169
|
+
const raw = await promptDefault(rl, 'Production dashboard URL (optional — skip for CloudFront-default)', '');
|
|
170
|
+
if (raw && !BARE_DOMAIN_RE.test(raw.trim())) {
|
|
171
|
+
console.warn('[tib] Invalid domain format — must be a bare domain like "app.acme.com". Skipping.');
|
|
172
|
+
} else {
|
|
173
|
+
prodUrl = raw.trim();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const defaultOutDir = opts.outDir ? resolve(opts.outDir) : resolve(process.cwd(), projectName);
|
|
178
|
+
const outputDirRaw = await promptDefault(rl, 'Output directory', defaultOutDir);
|
|
179
|
+
const outputDir = resolve(outputDirRaw);
|
|
180
|
+
|
|
181
|
+
// 2. Fetch versions.json
|
|
182
|
+
console.log('\nFetching scaffold versions...');
|
|
183
|
+
const versionsJson = await fetchVersionsJson(SCAFFOLD_BUCKET);
|
|
184
|
+
const scaffoldVersion = await promptDefault(rl, 'Scaffold version', versionsJson.latest);
|
|
185
|
+
|
|
186
|
+
rl.close();
|
|
187
|
+
|
|
188
|
+
// 3. Fetch modules.json
|
|
189
|
+
console.log(`\nFetching modules for scaffold@${scaffoldVersion}...`);
|
|
190
|
+
const modulesJson = await fetchModulesJson(SCAFFOLD_BUCKET, scaffoldVersion);
|
|
191
|
+
|
|
192
|
+
// 4. Resolve module selection
|
|
193
|
+
const selectedIds = new Set<string>(['core', 'backend-lambda']);
|
|
194
|
+
if (includeDatabase) selectedIds.add('db-aurora');
|
|
195
|
+
if (includeAuth) selectedIds.add('auth-cognito');
|
|
196
|
+
if (includeFrontend) selectedIds.add('frontend');
|
|
197
|
+
if (includeFlows) selectedIds.add('flows');
|
|
198
|
+
|
|
199
|
+
// Expand dependencies
|
|
200
|
+
const moduleMap = new Map(modulesJson.modules.map((m) => [m.id, m]));
|
|
201
|
+
let changed = true;
|
|
202
|
+
while (changed) {
|
|
203
|
+
changed = false;
|
|
204
|
+
for (const id of [...selectedIds]) {
|
|
205
|
+
const mod = moduleMap.get(id);
|
|
206
|
+
if (!mod) continue;
|
|
207
|
+
for (const dep of mod.dependencies) {
|
|
208
|
+
if (!selectedIds.has(dep)) {
|
|
209
|
+
selectedIds.add(dep);
|
|
210
|
+
changed = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const orderedModules = topoSort(modulesJson.modules, selectedIds);
|
|
217
|
+
|
|
218
|
+
// 5. Prepare output directory
|
|
219
|
+
await mkdir(outputDir, { recursive: true });
|
|
220
|
+
await mkdir(join(outputDir, '.tib'), { recursive: true });
|
|
221
|
+
|
|
222
|
+
// 6. Get CLI version from package.json
|
|
223
|
+
let cliVersion = '0.0.0';
|
|
224
|
+
try {
|
|
225
|
+
const { createRequire } = await import('node:module');
|
|
226
|
+
const require = createRequire(import.meta.url);
|
|
227
|
+
const pkgJson = require('../../package.json') as { version: string };
|
|
228
|
+
cliVersion = pkgJson.version;
|
|
229
|
+
} catch {
|
|
230
|
+
// ignore — fallback to 0.0.0
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 7. Build token context
|
|
234
|
+
const tokenCtx = buildTokenContext(
|
|
235
|
+
{ projectName, awsRegion, awsAccountId, adminEmail },
|
|
236
|
+
scaffoldVersion,
|
|
237
|
+
cliVersion
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// 8. Create manifest
|
|
241
|
+
const manifest: TibManifest = createManifest(
|
|
242
|
+
scaffoldVersion,
|
|
243
|
+
projectName,
|
|
244
|
+
awsRegion,
|
|
245
|
+
[...selectedIds]
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// 9. Process each module in topological order
|
|
249
|
+
for (const mod of orderedModules) {
|
|
250
|
+
console.log(` Installing module: ${mod.id}@${mod.version}...`);
|
|
251
|
+
|
|
252
|
+
const tarBuf = await fetchModuleTarball(
|
|
253
|
+
SCAFFOLD_BUCKET,
|
|
254
|
+
scaffoldVersion,
|
|
255
|
+
mod.tarball,
|
|
256
|
+
mod.sha256
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const tarEntries = await parseTarball(tarBuf);
|
|
260
|
+
|
|
261
|
+
// Extract MANIFEST.json from tarball
|
|
262
|
+
const manifestEntry = getTarEntry(tarEntries, 'MANIFEST.json');
|
|
263
|
+
if (!manifestEntry) {
|
|
264
|
+
cliLogger.warn({ moduleId: mod.id }, 'No MANIFEST.json in tarball — skipping');
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const tarManifest = JSON.parse(manifestEntry.content.toString('utf-8')) as TarballManifestEntry[];
|
|
269
|
+
|
|
270
|
+
// installPrefix for modules like 'frontend'
|
|
271
|
+
const modMeta = moduleMap.get(mod.id);
|
|
272
|
+
const installPrefix: string = (modMeta as (S3ModuleEntry & { installPrefix?: string }) | undefined)?.installPrefix ?? '';
|
|
273
|
+
|
|
274
|
+
for (const fileEntry of tarManifest) {
|
|
275
|
+
const tarEntry = getTarEntry(tarEntries, fileEntry.path);
|
|
276
|
+
if (!tarEntry) {
|
|
277
|
+
cliLogger.warn({ moduleId: mod.id, path: fileEntry.path }, 'File listed in MANIFEST.json not found in tarball');
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Determine install path
|
|
282
|
+
let installPath = fileEntry.installedAs ?? fileEntry.path;
|
|
283
|
+
// Strip .tpl extension
|
|
284
|
+
if (installPath.endsWith('.tpl')) {
|
|
285
|
+
installPath = installPath.slice(0, -4);
|
|
286
|
+
}
|
|
287
|
+
// Apply installPrefix
|
|
288
|
+
if (installPrefix) {
|
|
289
|
+
installPath = `${installPrefix}/${installPath}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Process content
|
|
293
|
+
let content = tarEntry.content.toString('utf-8');
|
|
294
|
+
|
|
295
|
+
// Token substitution for template files
|
|
296
|
+
if (fileEntry.isTemplate) {
|
|
297
|
+
content = applyTokens(content, tokenCtx);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Inject scaffold header
|
|
301
|
+
content = injectHeader(content, installPath, mod.id, mod.version);
|
|
302
|
+
|
|
303
|
+
// Write file
|
|
304
|
+
const destPath = join(outputDir, installPath);
|
|
305
|
+
const parentParts = installPath.split('/').slice(0, -1);
|
|
306
|
+
if (parentParts.length > 0) {
|
|
307
|
+
await mkdir(join(outputDir, ...parentParts), { recursive: true });
|
|
308
|
+
}
|
|
309
|
+
await writeFile(destPath, content, 'utf-8');
|
|
310
|
+
|
|
311
|
+
// Track in manifest (SHA-256 of written content)
|
|
312
|
+
const sha256 = computeChecksumString(content);
|
|
313
|
+
upsertManifestFile(manifest, {
|
|
314
|
+
path: installPath,
|
|
315
|
+
module: mod.id,
|
|
316
|
+
moduleVersion: mod.version,
|
|
317
|
+
sha256,
|
|
318
|
+
wasTemplate: fileEntry.isTemplate,
|
|
319
|
+
installedAt: new Date().toISOString(),
|
|
320
|
+
policy: fileEntry.policy ?? 'tracked',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 10. Write .mc/scaffold-config.json
|
|
326
|
+
const environments: EnvironmentsConfig | undefined = prodUrl
|
|
327
|
+
? { prod: { dashboardUrl: prodUrl } }
|
|
328
|
+
: undefined;
|
|
329
|
+
|
|
330
|
+
const scaffoldConfig: ScaffoldConfig = {
|
|
331
|
+
scaffoldVersion,
|
|
332
|
+
enabledModules: [...selectedIds] as ModuleId[],
|
|
333
|
+
projectName,
|
|
334
|
+
awsRegion,
|
|
335
|
+
awsAccountId,
|
|
336
|
+
adminEmail,
|
|
337
|
+
domainIds: [],
|
|
338
|
+
flowIds: [],
|
|
339
|
+
scaffoldBucket: SCAFFOLD_BUCKET,
|
|
340
|
+
...(environments ? { environments } : {}),
|
|
341
|
+
};
|
|
342
|
+
await writeScaffoldConfig(outputDir, scaffoldConfig);
|
|
343
|
+
|
|
344
|
+
// 11. Write .mc/manifest.json
|
|
345
|
+
await writeManifest(outputDir, manifest);
|
|
346
|
+
|
|
347
|
+
// 12. Git init + initial commit
|
|
348
|
+
console.log('\nInitialising git repository...');
|
|
349
|
+
execSync(`git -C "${outputDir}" init`, { stdio: 'pipe' });
|
|
350
|
+
execSync(`git -C "${outputDir}" add -A`, { stdio: 'pipe' });
|
|
351
|
+
execSync(
|
|
352
|
+
`git -C "${outputDir}" commit -m "chore: initial scaffold from mc-scaffold@${scaffoldVersion}"`,
|
|
353
|
+
{ stdio: 'pipe' }
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
cliLogger.info({ projectName, outputDir, scaffoldVersion }, 'Project created');
|
|
357
|
+
console.log(
|
|
358
|
+
`\nProject "${projectName}" created at ${outputDir}\n\n` +
|
|
359
|
+
` Next steps:\n` +
|
|
360
|
+
` cd ${outputDir}\n` +
|
|
361
|
+
` npm install\n` +
|
|
362
|
+
` npm run dev\n`
|
|
363
|
+
);
|
|
364
|
+
} finally {
|
|
365
|
+
rl.close();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { buildRegistry } from '../builder/build-registry.js';
|
|
3
|
+
import { createApiServer } from '../server/api-server.js';
|
|
4
|
+
import { cliLogger } from '../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for the dev command.
|
|
8
|
+
*/
|
|
9
|
+
export interface DevOptions {
|
|
10
|
+
/** Absolute or relative path to the domain root directory. */
|
|
11
|
+
domainRoot: string;
|
|
12
|
+
/** Port for the local HTTP server. Defaults to 3000. */
|
|
13
|
+
port?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Execute the dev command: build the domain registry and start the local Fastify
|
|
18
|
+
* HTTP server. Keeps the process running. Shuts down gracefully on SIGINT/SIGTERM.
|
|
19
|
+
* @param options - DevOptions specifying domain root and optional port.
|
|
20
|
+
*/
|
|
21
|
+
export async function runDev(options: DevOptions): Promise<void> {
|
|
22
|
+
const domainRoot = resolve(options.domainRoot);
|
|
23
|
+
const port = options.port ?? 3000;
|
|
24
|
+
|
|
25
|
+
cliLogger.info({ domainRoot }, 'Building registry for dev server');
|
|
26
|
+
const { registry, warnings } = await buildRegistry(domainRoot);
|
|
27
|
+
|
|
28
|
+
for (const w of warnings) {
|
|
29
|
+
cliLogger.warn(w);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cliLogger.info(
|
|
33
|
+
{ domain: registry.domain.id, apis: registry.apis.length, port },
|
|
34
|
+
'Starting local dev server'
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const server = await createApiServer({ port, registry, domainRoot });
|
|
38
|
+
|
|
39
|
+
async function shutdown(): Promise<void> {
|
|
40
|
+
cliLogger.info('Shutting down dev server');
|
|
41
|
+
await server.close();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
process.on('SIGINT', () => { void shutdown(); });
|
|
46
|
+
process.on('SIGTERM', () => { void shutdown(); });
|
|
47
|
+
}
|