diff --git a/scripts/cli-light-import-boundary-contract-test.ts b/scripts/cli-light-import-boundary-contract-test.ts new file mode 100644 index 00000000..e395ecfb --- /dev/null +++ b/scripts/cli-light-import-boundary-contract-test.ts @@ -0,0 +1,55 @@ +import { readFileSync } from "node:fs"; + +function assertCondition(condition: unknown, message: string, detail: unknown = {}): void { + if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`); +} + +function source(path: string): string { + return readFileSync(path, "utf8"); +} + +function staticImports(text: string): string[] { + return [...text.matchAll(/^import\s+(?:type\s+)?(?:[\s\S]*?)\s+from\s+["']([^"']+)["'];/gm)].map((match) => match[1] ?? ""); +} + +const cli = source("scripts/cli.ts"); +const help = source("scripts/src/help.ts"); + +const cliStaticImports = staticImports(cli); +const helpStaticImports = staticImports(help); + +const forbiddenCliStaticImports = ["./src/e2e", "./src/hwlab-cd", "./src/hwlab-g14", "./src/hwlab-node", "./src/agentrun", "./src/platform-infra"]; +const forbiddenHelpStaticImports = ["./hwlab-cd", "./hwlab-g14", "./hwlab-node", "./agentrun", "./platform-infra"]; + +for (const modulePath of forbiddenCliStaticImports) { + assertCondition(!cliStaticImports.includes(modulePath), "ordinary CLI entry must not statically import heavy command modules", { modulePath, cliStaticImports }); +} + +for (const modulePath of forbiddenHelpStaticImports) { + assertCondition(!helpStaticImports.includes(modulePath), "generic help module must not statically import heavy command modules", { modulePath, helpStaticImports }); +} + +for (const modulePath of forbiddenCliStaticImports) { + assertCondition(cli.includes(`await import("${modulePath}")`), "heavy CLI command module must still be loaded on demand", { modulePath }); +} + +for (const modulePath of forbiddenHelpStaticImports) { + assertCondition(help.includes(`await import("${modulePath}")`), "heavy help module must still be loaded on demand", { modulePath }); +} + +const ghBranchIndex = cli.indexOf('if (top === "gh")'); +const configReadIndex = cli.indexOf("const config = readConfig();"); +assertCondition(ghBranchIndex !== -1 && configReadIndex !== -1 && ghBranchIndex < configReadIndex, "gh commands must dispatch before generic config/runtime initialization", { + ghBranchIndex, + configReadIndex, +}); + +console.log(JSON.stringify({ + ok: true, + checks: [ + "cli static imports exclude e2e/HWLAB/G14/AgentRun/platform-infra modules", + "help static imports exclude HWLAB/G14/AgentRun/platform-infra modules", + "heavy command modules remain available through dynamic imports", + "gh dispatch remains before generic config initialization", + ], +})); diff --git a/scripts/cli.ts b/scripts/cli.ts index 81f75682..80269d5c 100644 --- a/scripts/cli.ts +++ b/scripts/cli.ts @@ -1,7 +1,6 @@ import { readConfig } from "./src/config"; import { debugDispatch, debugHealth, debugTask, isDebugDispatchCommand, type DebugDispatchCommand } from "./src/debug"; import { isRebuildableService, rebuildService, stackLogs, stackStatus, startStack, stopStack, unsupportedRebuildService } from "./src/docker"; -import { parseE2ERunOptions, runE2E } from "./src/e2e"; import { emitError, emitJson } from "./src/output"; import { cancelJob, jobWithTail, listJobs, listJobsSummary, readJob, runJob } from "./src/jobs"; import { checkHelp, parseCheckOptions, runChecks, runRecoveryGuardrailsCheck } from "./src/check"; @@ -24,12 +23,7 @@ import { isGhContentRoute, runGhContentRoute } from "./src/gh-route"; import { runCommanderCommand } from "./src/commander"; import { isHelpToken, rootHelp, serverHelp, sshHelp, staticNamespaceHelp } from "./src/help"; import { runServerCleanupCommand } from "./src/server-cleanup"; -import { runHwlabCdCommand } from "./src/hwlab-cd"; -import { runHwlabG14Command } from "./src/hwlab-g14"; -import { runHwlabNodeCommand } from "./src/hwlab-node"; import { runGcCommand } from "./src/gc"; -import { runAgentRunCommand } from "./src/agentrun"; -import { runPlatformInfraCommand } from "./src/platform-infra"; const remoteOptions = extractRemoteCliOptions(process.argv.slice(2)); const args = remoteOptions.args; @@ -235,7 +229,7 @@ async function main(): Promise { return; } - const namespaceHelp = staticNamespaceHelp(args); + const namespaceHelp = await staticNamespaceHelp(args); if (namespaceHelp !== null) { emitJson(commandName, namespaceHelp); return; @@ -293,6 +287,7 @@ async function main(): Promise { if (top === "hwlab") { if (sub === "node" || sub === "nodes") { + const { runHwlabNodeCommand } = await import("./src/hwlab-node"); const result = await runHwlabNodeCommand(readConfig(), args.slice(2)); const ok = (result as { ok?: unknown }).ok !== false; emitJson(commandName, result, ok); @@ -300,12 +295,14 @@ async function main(): Promise { return; } if (sub === "g14") { + const { runHwlabG14Command } = await import("./src/hwlab-g14"); const result = await runHwlabG14Command(readConfig(), args.slice(2)); const ok = (result as { ok?: unknown }).ok !== false; emitJson(commandName, result, ok); if (!ok) process.exitCode = 1; return; } + const { runHwlabCdCommand } = await import("./src/hwlab-cd"); const result = await runHwlabCdCommand(args.slice(1)); const ok = (result as { ok?: unknown }).ok !== false; emitJson(commandName, result, ok); @@ -314,6 +311,7 @@ async function main(): Promise { } if (top === "agentrun") { + const { runAgentRunCommand } = await import("./src/agentrun"); const result = await runAgentRunCommand(readConfig(), args.slice(1)); const ok = (result as { ok?: unknown }).ok !== false; emitJson(commandName, result, ok); @@ -322,6 +320,7 @@ async function main(): Promise { } if (top === "platform-infra") { + const { runPlatformInfraCommand } = await import("./src/platform-infra"); const result = await runPlatformInfraCommand(readConfig(), args.slice(1)); const ok = (result as { ok?: unknown }).ok !== false; emitJson(commandName, result, ok); @@ -525,6 +524,7 @@ async function main(): Promise { } if (top === "e2e" && sub === "run") { + const { parseE2ERunOptions, runE2E } = await import("./src/e2e"); const result = await runE2E(config, parseE2ERunOptions(args.slice(2))); const ok = (result as { ok?: unknown }).ok === true; emitJson(commandName, result, ok); diff --git a/scripts/src/help.ts b/scripts/src/help.ts index f8ff3efc..ebbbd08f 100644 --- a/scripts/src/help.ts +++ b/scripts/src/help.ts @@ -1,10 +1,5 @@ import { ghHelp } from "./gh"; import { authBrokerHelp } from "./auth-broker"; -import { hwlabHelp } from "./hwlab-cd"; -import { hwlabG14Help } from "./hwlab-g14"; -import { hwlabNodeHelp } from "./hwlab-node"; -import { agentRunHelp } from "./agentrun"; -import { platformInfraHelp } from "./platform-infra"; export function rootHelp(): unknown { return { @@ -600,7 +595,7 @@ function artifactRegistryHelp(): unknown { }; } -export function staticNamespaceHelp(args: string[]): unknown | null { +export async function staticNamespaceHelp(args: string[]): Promise { const [top, sub] = args; if (!args.slice(1).some(isHelpToken)) return null; if (top === "config") return configHelp(); @@ -619,10 +614,25 @@ export function staticNamespaceHelp(args: string[]): unknown | null { if (top === "artifact-registry") return artifactRegistryHelp(); if (top === "auth-broker") return authBrokerHelp(); if (top === "gh") return ghHelp(); - if (top === "agentrun") return agentRunHelp(); - if (top === "platform-infra") return platformInfraHelp(); - if (top === "hwlab" && (sub === "node" || sub === "nodes")) return hwlabNodeHelp(); - if (top === "hwlab" && sub === "g14") return hwlabG14Help(); - if (top === "hwlab") return hwlabHelp(); + if (top === "agentrun") { + const { agentRunHelp } = await import("./agentrun"); + return agentRunHelp(); + } + if (top === "platform-infra") { + const { platformInfraHelp } = await import("./platform-infra"); + return platformInfraHelp(); + } + if (top === "hwlab" && (sub === "node" || sub === "nodes")) { + const { hwlabNodeHelp } = await import("./hwlab-node"); + return hwlabNodeHelp(); + } + if (top === "hwlab" && sub === "g14") { + const { hwlabG14Help } = await import("./hwlab-g14"); + return hwlabG14Help(); + } + if (top === "hwlab") { + const { hwlabHelp } = await import("./hwlab-cd"); + return hwlabHelp(); + } return null; }