diff --git a/cli/src/commands/profile.ts b/cli/src/commands/profile.ts index 286b056..3a50712 100644 --- a/cli/src/commands/profile.ts +++ b/cli/src/commands/profile.ts @@ -484,7 +484,7 @@ export const profileCommand = defineLocalCommand, void>( "altertable profile --configure --api-key atm_xxx --env production", "altertable profile --configure --scope lakehouse", "altertable profile list", - "altertable profile create acme_prod --org acme --env production", + "altertable profile create acme_prod --api-key atm_xxx --env production", "altertable profile use acme_prod", "altertable profile switch", "altertable profile status", diff --git a/cli/src/features/profile/render.ts b/cli/src/features/profile/render.ts index 217c83b..e1d6935 100644 --- a/cli/src/features/profile/render.ts +++ b/cli/src/features/profile/render.ts @@ -19,7 +19,6 @@ import { } from "@/features/profile/model.ts"; import type { ConfigureAuthPlane } from "@/lib/profile-status.ts"; import { ConfigurationError } from "@/lib/errors.ts"; -import type { DisplayDocument } from "@/ui/document.ts"; import { renderDocument, renderDocumentText, renderRows } from "@/ui/renderers/terminal.ts"; import { nestedIndent, @@ -67,18 +66,6 @@ function renderConfigureRows( }); } -export function renderConfigureShowView( - document: DisplayDocument, - options: FormatConfigureAuthenticationOptions = {}, -): string[] { - return renderDocument(document, { - indent: options.indent ?? TERMINAL_INDENT, - labelWidth: options.labelWidth ?? TERMINAL_LABEL_WIDTH, - nestedIndent: nestedIndent(options.indent), - nestedLabelWidth: TERMINAL_NESTED_LABEL_WIDTH, - }); -} - export function formatConfigureAuthenticationLines( options: FormatConfigureAuthenticationOptions = {}, ): string[] { diff --git a/cli/src/lib/auth.ts b/cli/src/lib/auth.ts index 5de8620..e713167 100644 --- a/cli/src/lib/auth.ts +++ b/cli/src/lib/auth.ts @@ -66,7 +66,7 @@ export function getLakehouseAuthHeader(): string { } throw new ConfigurationError( - "No credentials. Run 'altertable profile --configure' or set ALTERTABLE_LAKEHOUSE_USERNAME/PASSWORD (or ALTERTABLE_BASIC_AUTH_TOKEN).", + "No credentials. Run 'altertable login', 'altertable profile --configure', or set ALTERTABLE_LAKEHOUSE_USERNAME/PASSWORD (or ALTERTABLE_BASIC_AUTH_TOKEN).", ); } diff --git a/cli/src/lib/profile-configure-core.ts b/cli/src/lib/profile-configure-core.ts index 96fb6ce..8451395 100644 --- a/cli/src/lib/profile-configure-core.ts +++ b/cli/src/lib/profile-configure-core.ts @@ -7,10 +7,7 @@ import { getCliContext, setCliContext } from "@/context.ts"; import { secretDelete, secretSet } from "@/lib/secrets.ts"; import { assertAllowedApiBase } from "@/lib/url-policy.ts"; import { getCliRuntime, getOutputSink, type OutputSink } from "@/lib/runtime.ts"; -import { buildConfigureShowData } from "@/features/profile/model.ts"; -import { buildConfigureShowView } from "@/features/profile/views.ts"; -import { renderConfigureShowView } from "@/features/profile/render.ts"; -import { formatTerminalSection, terminalMetadata } from "@/ui/terminal/styles.ts"; +import { terminalMetadata } from "@/ui/terminal/styles.ts"; const ARGV_SECRET_WARNING = "Warning: passing secrets on the command line is visible in process listings. Prefer --password-stdin / --api-key-stdin."; @@ -223,37 +220,6 @@ export async function configureRunSet( }); } -export function buildConfigureShowDataForProfile(profileOverride?: string) { - return withProfileContextSync(profileOverride ?? getCliContext().profile, () => - buildConfigureShowData(profileOverride), - ); -} - -function configureRunShowInternal(profileOverride?: string): string { - return withProfileContextSync(profileOverride ?? getCliContext().profile, () => { - const data = buildConfigureShowData(profileOverride); - return formatTerminalSection(renderConfigureShowView(buildConfigureShowView(data))); - }); -} - -function withProfileContextSync(profileName: string | undefined, run: () => T): T { - if (!profileName) { - return run(); - } - ensureProfileExists(profileName); - const previous = getCliContext(); - setCliContext({ ...previous, profile: profileName }); - try { - return run(); - } finally { - setCliContext(previous); - } -} - -export function configureRunShow(): string { - return configureRunShowInternal(); -} - export function configureRunClear(sink: OutputSink = getOutputSink()): void { const profiles = existsSync(profilesDir()) ? listProfiles() : []; for (const profile of profiles) { diff --git a/cli/tests/auth.test.ts b/cli/tests/auth.test.ts index 2ec0484..02e6668 100644 --- a/cli/tests/auth.test.ts +++ b/cli/tests/auth.test.ts @@ -35,7 +35,7 @@ describe("auth", () => { test("getLakehouseAuthHeader throws ConfigurationError when credentials are missing", () => { expect(() => getLakehouseAuthHeader()).toThrow(ConfigurationError); expect(() => getLakehouseAuthHeader()).toThrow( - "No credentials. Run 'altertable profile --configure' or set ALTERTABLE_LAKEHOUSE_USERNAME/PASSWORD (or ALTERTABLE_BASIC_AUTH_TOKEN).", + "No credentials. Run 'altertable login', 'altertable profile --configure', or set ALTERTABLE_LAKEHOUSE_USERNAME/PASSWORD (or ALTERTABLE_BASIC_AUTH_TOKEN).", ); }); diff --git a/cli/tests/config.test.ts b/cli/tests/config.test.ts index 8cb896d..5ba6e20 100644 --- a/cli/tests/config.test.ts +++ b/cli/tests/config.test.ts @@ -24,14 +24,7 @@ import { } from "@/lib/secrets.ts"; import { CliError } from "@/lib/errors.ts"; import { assertAllowedApiBase } from "@/lib/url-policy.ts"; -import { - configureClearAll, - configureRunClear, - configureRunSet, - configureRunShow, - buildConfigureShowDataForProfile, -} from "@/lib/profile-configure-core.ts"; -import { writeCommandOutput } from "@/lib/command-output.ts"; +import { configureRunClear, configureRunSet } from "@/lib/profile-configure-core.ts"; import { setCliContext } from "@/context.ts"; import { createCliRuntime, runWithCliRuntime } from "@/lib/runtime.ts"; @@ -139,100 +132,7 @@ describe("secrets", () => { }); }); -describe("configure show", () => { - test("masks secrets in show output", () => { - configSet("user", "alice"); - secretSet("lakehouse/password", "secret"); - const output = configureRunShow(); - expect(output).toContain("alice"); - expect(output).toContain("password:"); - expect(output).toContain("set"); - expect(output).not.toContain("secret"); - }); - - test("shows empty state after clear", () => { - configureClearAll(); - expect(configureRunShow()).toContain("No credentials configured"); - }); - - test("shows lakehouse and management credentials together", async () => { - await configureRunSet({ user: "alice", password: "lakehouse-secret" }); - await configureRunSet({ apiKey: "atm_test", env: "development" }); - - const output = configureRunShow(); - expect(output).toContain("Authentication:"); - expect(output).toContain("management API key"); - expect(output).toContain("environment:"); - expect(output).toContain("development"); - expect(output).toContain("lakehouse username/password"); - expect(output).toContain("user:"); - expect(output).toContain("alice"); - expect(output).not.toContain("lakehouse-secret"); - expect(output).not.toContain("atm_test"); - }); - - test("shows hint when only management credentials are configured", async () => { - await configureRunSet({ apiKey: "atm_test", env: "development" }); - const output = configureRunShow(); - expect(output).toContain("altertable profile --configure --scope lakehouse"); - expect(output).toContain("lakehouse query"); - }); - - test("shows hint when only lakehouse credentials are configured", async () => { - await configureRunSet({ user: "alice", password: "lakehouse-secret" }); - const output = configureRunShow(); - expect(output).toContain("altertable profile --configure --scope management"); - expect(output).toContain("profile show"); - }); - - test("shows two-step setup hint when no credentials are configured", () => { - configureClearAll(); - const output = configureRunShow(); - expect(output).toContain("No credentials configured"); - expect(output).toContain("altertable profile --configure --scope management"); - expect(output).toContain("altertable profile --configure --scope lakehouse"); - }); - - test("shows environment override when ALTERTABLE_ENV differs from stored", async () => { - await configureRunSet({ apiKey: "atm_test", env: "production" }); - process.env.ALTERTABLE_ENV = "staging"; - const output = configureRunShow(); - expect(output).toContain("Environment override:"); - expect(output).toContain("ALTERTABLE_ENV=staging (stored: production)"); - expect(output).not.toContain("atm_test"); - }); - - test("shows API key override line without printing the secret", async () => { - await configureRunSet({ apiKey: "atm_test", env: "production" }); - process.env.ALTERTABLE_API_KEY = "atm_override_secret"; - const output = configureRunShow(); - expect(output).toContain("API key override:"); - expect(output).toContain("ALTERTABLE_API_KEY is set via environment"); - expect(output).not.toContain("atm_override_secret"); - expect(output).not.toContain("atm_test"); - }); - - test("emits structured configuration envelope in json mode", async () => { - await configureRunSet({ apiKey: "atm_test", env: "production" }); - const stdout: string[] = []; - const runtime = createCliRuntime({ debug: false, json: true, agent: false }); - runtime.output.writeJson = (data) => { - stdout.push(JSON.stringify(data, null, 2)); - }; - await runWithCliRuntime(runtime, async () => { - await writeCommandOutput({ - kind: "normalized", - data: { configuration: buildConfigureShowDataForProfile() }, - humanText: configureRunShow(), - }); - }); - const parsed = JSON.parse(stdout[0] ?? "{}") as { - configuration?: { credentials?: { management?: { api_key?: string } } }; - }; - expect(parsed.configuration?.credentials?.management?.api_key).toBe("set"); - expect(stdout[0]).not.toContain("atm_test"); - }); - +describe("profile --configure credential accumulation", () => { test("preserves management credentials when updating lakehouse credentials", async () => { await configureRunSet({ apiKey: "atm_test", env: "development" }); await configureRunSet({ user: "alice", password: "lakehouse-secret" }); @@ -260,7 +160,7 @@ describe("configure show", () => { }); }); -describe("configure validation", () => { +describe("profile --configure validation", () => { test("rejects mixing lakehouse and management credentials in one invocation", async () => { return expect( configureRunSet({ user: "u", password: "p", apiKey: "atm_x", env: "prod" }), @@ -286,7 +186,7 @@ describe("configure validation", () => { }); }); -describe("configure clear", () => { +describe("configureRunClear", () => { test("configureRunClear removes root config and credentials files", async () => { await configureRunSet({ user: "alice", password: "lakehouse-secret" }); configureRunClear(); @@ -343,7 +243,7 @@ describe("url policy", () => { }); }); -describe("configure argv secrets", () => { +describe("profile --configure argv secrets", () => { test("warns when password is passed on argv", async () => { const stderr: string[] = []; const runtime = createCliRuntime({ debug: false, json: false, agent: false }); diff --git a/cli/tests/configure-credential-status.test.ts b/cli/tests/configure-credential-status.test.ts index fe320c9..aaf1d26 100644 --- a/cli/tests/configure-credential-status.test.ts +++ b/cli/tests/configure-credential-status.test.ts @@ -181,7 +181,7 @@ describe("buildConfigureShowData", () => { expect(JSON.stringify(data)).not.toContain("atm_override"); }); - test("configure show view separates summary and authentication sections", () => { + test("buildConfigureShowView separates summary and authentication sections", () => { secretSet("api-key", "atm_test"); configSet("api_key_env", "production"); diff --git a/cli/tests/configure-data-plane-url.test.ts b/cli/tests/configure-data-plane-url.test.ts index 456674e..2314cd3 100644 --- a/cli/tests/configure-data-plane-url.test.ts +++ b/cli/tests/configure-data-plane-url.test.ts @@ -30,7 +30,7 @@ afterEach(async () => { delete process.env.ALTERTABLE_SECRET_BACKEND; }); -describe("configure --data-plane-url alone", () => { +describe("profile --configure --data-plane-url alone", () => { test("sets api_base and nothing else", async () => { await runInTestHome(async () => { await configureRunSet({ apiKey: "atm_test", env: "staging" }); diff --git a/cli/tests/profile.test.ts b/cli/tests/profile.test.ts index a3dd701..6767726 100644 --- a/cli/tests/profile.test.ts +++ b/cli/tests/profile.test.ts @@ -103,7 +103,7 @@ describe("profile storage", () => { expect(deriveProfileName("acme", "prod/eu")).toBe("acme_prod-eu"); }); - test("configure writes credentials to an explicit profile", async () => { + test("profile --configure writes credentials to an explicit profile", async () => { await configureRunSet({ profile: "acme_production", apiKey: "atm_prod", env: "Production" }); expect(profileExists("acme_production")).toBe(true); @@ -165,7 +165,7 @@ describe("profile storage", () => { expect(profile.timestamps.lakehouse_expires_at).toBe(new Date(lakehouseExpiry).toISOString()); }); - test("configure creates implicit profile directories", async () => { + test("profile --configure creates implicit profile directories", async () => { await configureRunSet({ profile: "staging", apiKey: "atm_staging", env: "staging" }); expect(profileExists("staging")).toBe(true); setCliContext({ debug: false, json: false, agent: false, profile: "staging" });