From 5f654731896a8c2aff276e791da28678598dd0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 16 Jul 2026 15:33:40 +0200 Subject: [PATCH] fix(env): defer validation until after early exits --- cli/src/cli.ts | 2 +- cli/src/lib/env.ts | 11 ----------- cli/tests/env.test.ts | 10 +++++++--- tests/helpers.ts | 6 +++++- tests/scripting.test.ts | 24 ++++++++++++++++++++++++ 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/cli/src/cli.ts b/cli/src/cli.ts index cecbd94..aff886c 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -163,7 +163,6 @@ async function bootstrap(): Promise { setCliContext(earlyContext); try { - validateEnvironment(); const earlyExit = findEarlyBootstrapExit(rawArgs); if (earlyExit?.id === "help") { const [command, parent] = await resolveSubCommandForUsage(main, rawArgs); @@ -180,6 +179,7 @@ async function bootstrap(): Promise { return; } + validateEnvironment(); await runCommand(main, { rawArgs }); await maybeShowUpdateNotice({ context: getCliContext(), diff --git a/cli/src/lib/env.ts b/cli/src/lib/env.ts index 3eb6444..96fd8d5 100644 --- a/cli/src/lib/env.ts +++ b/cli/src/lib/env.ts @@ -157,17 +157,6 @@ export function copyProcessEnv(): NodeJS.ProcessEnv { } export function validateEnvironment(source: EnvSource = process.env): void { - const knownNames = new Set(Object.keys(ENV_SCHEMA)); - const unknownNames = Object.keys(source) - .filter((name) => name.startsWith("ALTERTABLE_") && !knownNames.has(name)) - .sort(); - if (unknownNames.length > 0) { - const noun = unknownNames.length === 1 ? "variable" : "variables"; - throw new ConfigurationError( - `Unknown Altertable environment ${noun}: ${unknownNames.join(", ")}. Check the spelling against the documented ALTERTABLE_* variables.`, - ); - } - for (const name of Object.keys(ENV_SCHEMA) as EnvName[]) { if (name.startsWith("ALTERTABLE_")) { readEnvFrom(source, name); diff --git a/cli/tests/env.test.ts b/cli/tests/env.test.ts index 734439e..33a12f2 100644 --- a/cli/tests/env.test.ts +++ b/cli/tests/env.test.ts @@ -77,10 +77,14 @@ describe("environment schema", () => { ).toThrow("Secret environment variables must declare secret: true: ALTERTABLE_NEW_TOKEN"); }); - test("rejects misspelled Altertable variable names", () => { + test("ignores unknown Altertable variable names", () => { expect(() => - validateEnvironment({ ALTERTABLE_API_KEI: "secret", ALTERTABLE_API_KEY: "valid" }), - ).toThrow("Unknown Altertable environment variable: ALTERTABLE_API_KEI"); + validateEnvironment({ + ALTERTABLE_CATALOG: "analytics", + ALTERTABLE_SCHEMA: "reporting", + ALTERTABLE_TABLE: "events", + }), + ).not.toThrow(); }); test("validates every configured Altertable value at startup", () => { diff --git a/tests/helpers.ts b/tests/helpers.ts index 0ad4149..2fc0394 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -12,6 +12,7 @@ type TestEnvName = | "ALTERTABLE_API_BASE" | "ALTERTABLE_API_KEY" | "ALTERTABLE_BASIC_AUTH_TOKEN" + | "ALTERTABLE_CATALOG" | "ALTERTABLE_CONFIG_HOME" | "ALTERTABLE_ENV" | "ALTERTABLE_HTTP_LOG" @@ -20,7 +21,10 @@ type TestEnvName = | "ALTERTABLE_MANAGEMENT_API_BASE" | "ALTERTABLE_MOCK_HTTP_FILE" | "ALTERTABLE_PROFILE" - | "ALTERTABLE_SECRET_BACKEND"; + | "ALTERTABLE_SCHEMA" + | "ALTERTABLE_SECRET_BACKEND" + | "ALTERTABLE_TABLE" + | "ALTERTABLE_UPDATE_SOURCE"; export type TestEnv = Partial>; type RunOptions = { diff --git a/tests/scripting.test.ts b/tests/scripting.test.ts index 6b98820..b9f502a 100644 --- a/tests/scripting.test.ts +++ b/tests/scripting.test.ts @@ -32,6 +32,30 @@ describe("scriptable exit codes and JSON errors", () => { expect(JSON.parse(result.stdout).profile.name).toBe("_from_env"); }); + test("ignores unrelated Altertable workflow variables", async () => { + const result = await workspace.runCommand("altertable --json profile show", { + env: { + ALTERTABLE_CATALOG: "analytics", + ALTERTABLE_SCHEMA: "reporting", + ALTERTABLE_TABLE: "events", + }, + }); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(JSON.parse(result.stdout).profile.name).toBe("_from_env"); + }); + + test.each(["--help", "--version"])("%s bypasses environment validation", async (flag) => { + const result = await workspace.runCommand(`altertable ${flag}`, { + env: { ALTERTABLE_UPDATE_SOURCE: "gitlab" }, + }); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(result.stdout.length).toBeGreaterThan(0); + }); + test.each([ ["auth", statusMocks.auth, "altertable --json api GET /whoami", 2, "auth_failed"], ["not found", statusMocks.missing, "altertable --json api GET /environments/production/connections/missing", 4, undefined],