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
20 changes: 19 additions & 1 deletion src/env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("env subcommands", () => {
"--port",
"8443",
"-s",
"--no-verify",
]);

const config = await readConfig();
Expand All @@ -61,13 +62,30 @@ describe("env subcommands", () => {
test("env add merges with existing environment", async () => {
await setEnvironment("prod", { host: "old.com", user: "admin" });

await runEnvCommand(["add", "prod", "--host", "new.com"]);
await runEnvCommand(["add", "prod", "--host", "new.com", "--no-verify"]);

const config = await readConfig();
expect(config.environments.prod!.host).toBe("new.com");
expect(config.environments.prod!.user).toBe("admin");
});

test("env add rejects environment with no host or url", async () => {
const spy = spyOn(process, "exit").mockImplementation(() => {
throw new Error("process.exit");
});
const errSpy = spyOn(console, "error").mockImplementation(() => {});

await expect(
runEnvCommand(["add", "empty", "--no-verify"]),
).rejects.toThrow("process.exit");

spy.mockRestore();
errSpy.mockRestore();

const config = await readConfig();
expect(config.environments.empty).toBeUndefined();
});

test("env list shows environments", async () => {
await setEnvironment("prod", { host: "ch.prod.com" });
await setEnvironment("dev", { host: "localhost" });
Expand Down
52 changes: 49 additions & 3 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
getEnvironment,
type Environment,
} from "./config";
import { resolveConnectionConfig } from "./client";
import type { CliConfig } from "./cli";

export async function runEnvCommand(args: readonly string[]): Promise<void> {
const subcommand = args[0];
Expand Down Expand Up @@ -50,6 +52,7 @@ Add options:
-d, --database <db> Database
-s, --secure Use HTTPS
--url <url> ClickHouse URL (sets host, port, secure, password)
--no-verify Skip connection verification

Examples:
chcli env add prod --host ch.prod.com --port 8443 --secure -u admin
Expand All @@ -68,6 +71,7 @@ async function envAdd(args: readonly string[]): Promise<void> {
database: { type: "string", short: "d" },
secure: { type: "boolean", short: "s", default: false },
url: { type: "string" },
"no-verify": { type: "boolean", default: false },
},
allowPositionals: true,
strict: true,
Expand All @@ -89,13 +93,55 @@ async function envAdd(args: readonly string[]): Promise<void> {
if (values.url) env.url = values.url;

const existing = await getEnvironment(name);
const merged = existing ? { ...existing, ...env } : env;

// Require at least a host or url
if (!merged.host && !merged.url) {
console.error("Error: at least --host or --url is required");
process.exit(1);
}

// Verify connection unless --no-verify is passed
if (!values["no-verify"]) {
const { createClient } = await import("@clickhouse/client");

const connConfig = resolveConnectionConfig({} as CliConfig, {},merged);
const client = createClient(connConfig);
try {
await client.query({ query: "SELECT 1", format: "JSON" });
} catch (firstErr: any) {
// If not already secure, retry with HTTPS on port 8443
if (!merged.secure && !merged.url) {
const secureEnv = { ...merged, secure: true, port: merged.port || "8443" };
const secureConfig = resolveConnectionConfig({} as CliConfig, {},secureEnv);
const secureClient = createClient(secureConfig);
try {
await secureClient.query({ query: "SELECT 1", format: "JSON" });
merged.secure = true;
if (!merged.port) merged.port = "8443";
console.log("Detected HTTPS on port 8443 — added automatically");
} catch (secondErr: any) {
const msg = firstErr?.message ?? String(firstErr);
console.error(`Error: connection to ClickHouse failed\n${msg}`);
process.exit(1);
} finally {
await secureClient.close();
}
} else {
const msg = firstErr?.message ?? String(firstErr);
console.error(`Error: connection to ClickHouse failed\n${msg}`);
process.exit(1);
}
} finally {
await client.close();
}
}

if (existing) {
// Merge with existing — only override fields that were explicitly passed
const merged = { ...existing, ...env };
await setEnvironment(name, merged);
console.log(`Updated environment "${name}"`);
} else {
await setEnvironment(name, env);
await setEnvironment(name, merged);
console.log(`Added environment "${name}"`);
}
}
Expand Down
Loading