Skip to content

Commit 29eebb6

Browse files
committed
chore: use ini stringify
1 parent 1f11200 commit 29eebb6

File tree

3 files changed

+75
-28
lines changed

3 files changed

+75
-28
lines changed

packages/cli/src/cli/utils/settings.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from "path";
33
import _ from "lodash";
44
import Z from "zod";
55
import fs from "fs";
6-
import { getRcConfig } from "@lingo.dev/config";
6+
import { getRcConfig, saveRcConfig } from "@lingo.dev/config";
77
import { PROVIDER_METADATA } from "@lingo.dev/providers";
88

99
export type CliSettings = Z.infer<typeof SettingsSchema>;
@@ -131,30 +131,7 @@ function _loadSystemFile() {
131131
}
132132

133133
function _saveSystemFile(settings: CliSettings) {
134-
const settingsFilePath = _getSettingsFilePath();
135-
const llmEntries: string[] = [];
136-
for (const meta of Object.values(PROVIDER_METADATA)) {
137-
const cfgKey = meta.apiKeyConfigKey;
138-
if (!cfgKey) continue;
139-
const suffix = cfgKey.startsWith("llm.") ? cfgKey.slice(4) : undefined;
140-
if (!suffix) continue;
141-
const value = (settings.llm as any)?.[suffix];
142-
if (value) llmEntries.push(`${suffix}=${value}`);
143-
}
144-
145-
const content = [
146-
`[auth]`,
147-
`apiKey=${settings.auth.apiKey}`,
148-
`apiUrl=${settings.auth.apiUrl}`,
149-
`webUrl=${settings.auth.webUrl}`,
150-
``,
151-
`[llm]`,
152-
...llmEntries,
153-
``,
154-
]
155-
.filter(Boolean)
156-
.join("\n");
157-
fs.writeFileSync(settingsFilePath, content);
134+
saveRcConfig(settings);
158135
}
159136

160137
function _getSettingsFilePath(): string {

packages/config/src/index.spec.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,60 @@
1-
import { describe, it, expect } from "vitest";
1+
import { describe, it, expect, vi, beforeEach } from "vitest";
2+
import { saveRcConfig, getRcConfig } from "./index";
3+
import fs from "fs";
4+
import os from "os";
5+
import path from "path";
6+
7+
vi.mock("fs");
8+
vi.mock("os");
29

310
describe("config package", () => {
4-
it("loads without tests", () => {
5-
expect(true).toBe(true);
11+
beforeEach(() => {
12+
vi.clearAllMocks();
13+
vi.mocked(os.homedir).mockReturnValue("/mock/home");
14+
});
15+
16+
it("saveRcConfig uses Ini.stringify", () => {
17+
const mockWrite = vi.mocked(fs.writeFileSync);
18+
19+
saveRcConfig({
20+
auth: {
21+
apiKey: "test-key",
22+
apiUrl: "https://test.com",
23+
webUrl: "https://web.test.com",
24+
},
25+
llm: {
26+
openaiApiKey: "openai-key",
27+
},
28+
});
29+
30+
expect(mockWrite).toHaveBeenCalledWith(
31+
path.join("/mock/home", ".lingodotdevrc"),
32+
expect.stringContaining("[auth]")
33+
);
34+
expect(mockWrite).toHaveBeenCalledWith(
35+
path.join("/mock/home", ".lingodotdevrc"),
36+
expect.stringContaining("[llm]")
37+
);
38+
});
39+
40+
it("saveRcConfig handles undefined values gracefully", () => {
41+
const mockWrite = vi.mocked(fs.writeFileSync);
42+
43+
saveRcConfig({
44+
auth: {
45+
apiKey: "test-key",
46+
},
47+
llm: {
48+
openaiApiKey: undefined,
49+
anthropicApiKey: "defined-key",
50+
},
51+
});
52+
53+
expect(mockWrite).toHaveBeenCalled();
54+
const writtenContent = mockWrite.mock.calls[0][1] as string;
55+
56+
// Ini.stringify should omit undefined values, not write "undefined"
57+
expect(writtenContent).not.toContain("undefined");
58+
expect(writtenContent).toContain("anthropicApiKey");
659
});
760
});

packages/config/src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,20 @@ export function getRcConfig(): RcConfig {
3131
const data = Ini.parse(content);
3232
return rcConfigSchema.parse(data);
3333
}
34+
35+
function removeUndefined<T>(obj: T): T {
36+
if (obj === null || typeof obj !== "object") return obj;
37+
if (Array.isArray(obj)) return obj.map(removeUndefined) as T;
38+
return Object.fromEntries(
39+
Object.entries(obj)
40+
.filter(([_, v]) => v !== undefined)
41+
.map(([k, v]) => [k, removeUndefined(v)])
42+
) as T;
43+
}
44+
45+
export function saveRcConfig(config: RcConfig): void {
46+
const settingsFilePath = getSettingsFilePath();
47+
// Ini.stringify writes literal "undefined" strings, so filter them out first
48+
const content = Ini.stringify(removeUndefined(config));
49+
fs.writeFileSync(settingsFilePath, content);
50+
}

0 commit comments

Comments
 (0)