Skip to content
Open
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
16 changes: 13 additions & 3 deletions src/commands/ConfigCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ export class ConfigCommand {
private static list(): void {
const settings = Config.load();
if (Output.isJson()) {
Output.json(settings);
Output.json(this.redactForDisplay(settings));
return;
}

const data = Object.entries(settings).map(([key, value]) => ({
const display = this.redactForDisplay(settings);
const data = Object.entries(display).map(([key, value]) => ({
setting: key,
value: value ? String(value) : "",
value: value != null && value !== "" ? String(value) : "",
}));
Output.table({
type: "horizontal",
Expand All @@ -42,6 +43,15 @@ export class ConfigCommand {
});
}

private static redactForDisplay(
settings: ReturnType<typeof Config.load>
): ReturnType<typeof Config.load> {
if (!settings.apiKey) {
return settings;
}
return { ...settings, apiKey: "<set>" };
}

private static set(opts: {
activeKey?: string;
output?: "table" | "json";
Expand Down
66 changes: 66 additions & 0 deletions src/lib/Config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

import { Config } from "./Config.ts";

const TEST_DIR = join(tmpdir(), `jup-config-test-${Date.now()}`);
const origSettingsFile = Config.SETTINGS_FILE;

beforeEach(() => {
mkdirSync(TEST_DIR, { recursive: true });
// @ts-expect-error test override
Config.SETTINGS_FILE = join(TEST_DIR, "settings.json");
});

afterEach(() => {
rmSync(TEST_DIR, { recursive: true, force: true });
// @ts-expect-error restore
Config.SETTINGS_FILE = origSettingsFile;
});

describe("Config.load", () => {
test("uses defaults when file is missing", () => {
const s = Config.load();
expect(s).toEqual({
activeKey: "default",
output: "table",
});
expect(s.apiKey).toBeUndefined();
});

test("throws a clear error when settings.json is not valid json", () => {
writeFileSync(
join(TEST_DIR, "settings.json"),
"{ broken json no closing brace",
"utf-8"
);
expect(() => Config.load()).toThrow(
/Could not parse .*settings\.json.*Fix the file or remove it/
);
});

test("loads valid settings", () => {
writeFileSync(
join(TEST_DIR, "settings.json"),
JSON.stringify(
{ activeKey: "main", output: "json", apiKey: "secret" },
null,
2
)
);
expect(Config.load()).toEqual({
activeKey: "main",
output: "json",
apiKey: "secret",
});
});

test("treats non-object root as empty object for fields", () => {
writeFileSync(join(TEST_DIR, "settings.json"), "[]");
const s = Config.load();
expect(s.activeKey).toBe("default");
expect(s.output).toBe("table");
});
});
25 changes: 19 additions & 6 deletions src/lib/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,30 @@ export class Config {
if (!existsSync(this.SETTINGS_FILE)) {
return { ...DEFAULT_SETTINGS };
}
const raw = JSON.parse(readFileSync(this.SETTINGS_FILE, "utf-8"));
const contents = readFileSync(this.SETTINGS_FILE, "utf-8");
let raw: unknown;
try {
raw = JSON.parse(contents) as unknown;
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
throw new Error(
`Could not parse ${this.SETTINGS_FILE}: ${message}. Fix the file or remove it to use defaults.`
);
}
const o =
typeof raw === "object" && raw !== null
? (raw as Record<string, unknown>)
: null;
return {
activeKey:
typeof raw.activeKey === "string"
? raw.activeKey
typeof o?.activeKey === "string"
? o.activeKey
: DEFAULT_SETTINGS.activeKey,
output:
raw.output === "table" || raw.output === "json"
? raw.output
o?.output === "table" || o?.output === "json"
? o.output
: DEFAULT_SETTINGS.output,
apiKey: typeof raw.apiKey === "string" ? raw.apiKey : undefined,
apiKey: typeof o?.apiKey === "string" ? o.apiKey : undefined,
};
}

Expand Down