Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/commands/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ export const profileCommand = defineLocalCommand<Record<string, unknown>, 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",
Expand Down
13 changes: 0 additions & 13 deletions cli/src/features/profile/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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[] {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).",
);
}

Expand Down
36 changes: 1 addition & 35 deletions cli/src/lib/profile-configure-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down Expand Up @@ -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<T>(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) {
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).",
);
});

Expand Down
110 changes: 5 additions & 105 deletions cli/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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" });
Expand Down Expand Up @@ -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" }),
Expand All @@ -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();
Expand Down Expand Up @@ -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 });
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/configure-credential-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
2 changes: 1 addition & 1 deletion cli/tests/configure-data-plane-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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" });
Expand Down
Loading