fix: isolate CLI startup imports (#50212)

* fix: isolate CLI startup imports

* fix: clarify CLI preflight behavior

* fix: tighten main-module detection

* fix: isolate CLI startup imports (#50212)
This commit is contained in:
Ayaan Zaidi
2026-03-19 10:34:29 +05:30
committed by GitHub
parent 68bc6effc0
commit d978ace90b
11 changed files with 218 additions and 187 deletions

View File

@@ -4,8 +4,8 @@ import type { RuntimeEnv } from "../../runtime.js";
const loadAndMaybeMigrateDoctorConfigMock = vi.hoisted(() => vi.fn());
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
vi.mock("../../commands/doctor-config-flow.js", () => ({
loadAndMaybeMigrateDoctorConfig: loadAndMaybeMigrateDoctorConfigMock,
vi.mock("../../commands/doctor-config-preflight.js", () => ({
runDoctorConfigPreflight: loadAndMaybeMigrateDoctorConfigMock,
}));
vi.mock("../../config/config.js", () => ({
@@ -58,12 +58,17 @@ describe("ensureConfigReady", () => {
}
function setInvalidSnapshot(overrides?: Partial<ReturnType<typeof makeSnapshot>>) {
readConfigFileSnapshotMock.mockResolvedValue({
const snapshot = {
...makeSnapshot(),
exists: true,
valid: false,
issues: [{ path: "channels.whatsapp", message: "invalid" }],
...overrides,
};
readConfigFileSnapshotMock.mockResolvedValue(snapshot);
loadAndMaybeMigrateDoctorConfigMock.mockResolvedValue({
snapshot,
baseConfig: {},
});
}
@@ -78,6 +83,10 @@ describe("ensureConfigReady", () => {
vi.clearAllMocks();
resetConfigGuardStateForTests();
readConfigFileSnapshotMock.mockResolvedValue(makeSnapshot());
loadAndMaybeMigrateDoctorConfigMock.mockImplementation(async () => ({
snapshot: makeSnapshot(),
baseConfig: {},
}));
});
it.each([
@@ -94,6 +103,13 @@ describe("ensureConfigReady", () => {
])("$name", async ({ commandPath, expectedDoctorCalls }) => {
await runEnsureConfigReady(commandPath);
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledTimes(expectedDoctorCalls);
if (expectedDoctorCalls > 0) {
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledWith({
migrateState: false,
migrateLegacyConfig: false,
invalidConfigNote: false,
});
}
});
it("exits for invalid config on non-allowlisted commands", async () => {
@@ -132,6 +148,10 @@ describe("ensureConfigReady", () => {
it("prevents preflight stdout noise when suppression is enabled", async () => {
loadAndMaybeMigrateDoctorConfigMock.mockImplementation(async () => {
process.stdout.write("Doctor warnings\n");
return {
snapshot: makeSnapshot(),
baseConfig: {},
};
});
const output = await withCapturedStdout(async () => {
await runEnsureConfigReady(["message"], true);
@@ -142,6 +162,10 @@ describe("ensureConfigReady", () => {
it("allows preflight stdout noise when suppression is not enabled", async () => {
loadAndMaybeMigrateDoctorConfigMock.mockImplementation(async () => {
process.stdout.write("Doctor warnings\n");
return {
snapshot: makeSnapshot(),
baseConfig: {},
};
});
const output = await withCapturedStdout(async () => {
await runEnsureConfigReady(["message"], false);

View File

@@ -39,22 +39,25 @@ export async function ensureConfigReady(params: {
suppressDoctorStdout?: boolean;
}): Promise<void> {
const commandPath = params.commandPath ?? [];
let preflightSnapshot: Awaited<ReturnType<typeof readConfigFileSnapshot>> | null = null;
if (!didRunDoctorConfigFlow && shouldMigrateStateFromPath(commandPath)) {
didRunDoctorConfigFlow = true;
const runDoctorConfigFlow = async () =>
(await import("../../commands/doctor-config-flow.js")).loadAndMaybeMigrateDoctorConfig({
options: { nonInteractive: true },
confirm: async () => false,
const runDoctorConfigPreflight = async () =>
(await import("../../commands/doctor-config-preflight.js")).runDoctorConfigPreflight({
// Keep ordinary CLI startup on the lightweight validation path.
migrateState: false,
migrateLegacyConfig: false,
invalidConfigNote: false,
});
if (!params.suppressDoctorStdout) {
await runDoctorConfigFlow();
preflightSnapshot = (await runDoctorConfigPreflight()).snapshot;
} else {
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
const originalSuppressNotes = process.env.OPENCLAW_SUPPRESS_NOTES;
process.stdout.write = (() => true) as unknown as typeof process.stdout.write;
process.env.OPENCLAW_SUPPRESS_NOTES = "1";
try {
await runDoctorConfigFlow();
preflightSnapshot = (await runDoctorConfigPreflight()).snapshot;
} finally {
process.stdout.write = originalStdoutWrite;
if (originalSuppressNotes === undefined) {
@@ -66,7 +69,7 @@ export async function ensureConfigReady(params: {
}
}
const snapshot = await getConfigSnapshot();
const snapshot = preflightSnapshot ?? (await getConfigSnapshot());
const commandName = commandPath[0];
const subcommandName = commandPath[1];
const allowInvalid = commandName