From 7a1e37eecede7326c85466de79a4e5f8798c83a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jozef=20Steinh=C3=BCbl?= Date: Tue, 8 Jul 2025 19:53:49 +0200 Subject: [PATCH] feat: use @iarna/toml --- bun.lockb | Bin 35576 -> 35922 bytes package.json | 3 +- src/bunfig.ts | 207 +++++++++++++------------------------------ tests/bunfig.spec.ts | 115 ++++++++++++++++++------ 4 files changed, 152 insertions(+), 173 deletions(-) diff --git a/bun.lockb b/bun.lockb index 9e5fbb0e497deb35a37f3dc1c626de320aa669a2..6ede1d51f75d8a4d442ea37ac14be578ead7ada9 100755 GIT binary patch delta 6095 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 MjX4;%VQ}0(0qTuE3IG5A delta 5960 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 Pkw(7T!jF0eCQ|; + }; + [key: string]: any; }; -export function createField(registry: Registry): Field { - const { url: registryUrl, scope, token } = registry; - - let url: URL | undefined; - if (registryUrl) { - try { - url = new URL(registryUrl); - } catch { - throw new Error(`Invalid registry url ${registryUrl}`); - } - } - - let owner: string | undefined; - if (scope) { - owner = scope.startsWith("@") - ? scope.toLocaleLowerCase() - : `@${scope.toLocaleLowerCase()}`; - } - - if (url && owner) { - return { - type: FieldType.INSTALL_WITH_SCOPE, - value: `'${owner}' = { url = "${url}"${ - token ? `, token = "${token}"` : "" - } }`, - }; - } - - if (url && !owner) { - return { - type: FieldType.GLOBAL_REGISTRY, - value: `registry = "${url}"`, - }; - } - - return null; -} - -export function createBunfig(registries: Registry[]): Field[] | null { - const fields = registries.map(createField).filter((field) => field); - if (fields.length === 0) { - return null; - } - - if ( - fields.filter((field) => field.type === FieldType.GLOBAL_REGISTRY).length > - 1 - ) { - throw new Error("You can't have more than one global registry."); - } - - return fields; -} - -export function serializeInstallScopes( - fields: Field[], - header: boolean = false -): string { - const installScopes = fields - .filter((field) => field.type === FieldType.INSTALL_WITH_SCOPE) - .map((field) => field.value) - .join(EOL); - - if (!installScopes) { - return ""; - } - - return `${header ? `[install.scopes]${EOL}` : ""}${installScopes}${EOL}`; -} - -export function serializeGlobalRegistry( - fields: Field[], - header: boolean = false -): string { - const globalRegistry = fields - .filter((field) => field.type === FieldType.GLOBAL_REGISTRY) - .map((field) => field.value) - .join(EOL); - - if (!globalRegistry) { - return ""; - } - - return `${header ? `[install]${EOL}` : ""}${globalRegistry}${EOL}`; -} - export function writeBunfig(path: string, registries: Registry[]): void { - const bunfig = createBunfig(registries); - if (!bunfig) { + if (!registries.length) { return; } + let globalRegistryCount = 0; + registries.forEach((registry) => { + try { + new URL(registry.url); + } catch { + throw new Error(`Invalid registry URL: ${registry.url}`); + } + + if (!registry.scope) { + globalRegistryCount++; + } + }); + + if (globalRegistryCount > 1) { + throw new Error("You can't have more than one global registry."); + } + info(`Writing bunfig.toml to '${path}'.`); - if (!existsSync(path)) { - writeFileSync( - path, - `${serializeGlobalRegistry(bunfig, true)}${serializeInstallScopes( - bunfig, - true - )}`, - { - encoding: "utf8", - } - ); - - return; + let config: BunfigConfig = {}; + if (existsSync(path)) { + try { + const content = readFileSync(path, { encoding: "utf-8" }); + config = parse(content) as BunfigConfig; + } catch (error) { + info(`Error reading existing bunfig: ${error.message}`); + config = {}; + } } - let newContent = ""; - const contents = readFileSync(path, { - encoding: "utf-8", - }).split(EOL); + config.install = config?.install || {}; + config.install.scopes = config?.install.scopes || {}; - contents.forEach((line, index, array) => { - if (index > 0 && array[index - 1].includes("[install.scopes]")) { - newContent += serializeInstallScopes(bunfig); - } - - if (index > 0 && array[index - 1].includes("[install]")) { - newContent += serializeGlobalRegistry(bunfig); - } - - if ( - line.startsWith("registry = ") || - !bunfig.some( - (field) => - field.type === FieldType.INSTALL_WITH_SCOPE && - (line.startsWith(field.value.split(" ")[0]) || - ((line[0] === "'" || line[0] === '"') && - line - .toLowerCase() - .startsWith(field.value.split(" ")[0].slice(1).slice(0, -1)))) - ) - ) { - newContent += line + EOL; - } - }); - - if (!contents.includes("[install.scopes]")) { - newContent += serializeInstallScopes(bunfig, true); + const globalRegistry = registries.find((r) => !r.scope); + if (globalRegistry) { + config.install.registry = { + url: globalRegistry.url, + ...(globalRegistry.token ? { token: globalRegistry.token } : {}), + }; } - if (!contents.includes("[install]")) { - newContent += serializeGlobalRegistry(bunfig, true); + for (const registry of registries) { + if (registry.scope) { + const scopeName = registry.scope.startsWith("@") + ? registry.scope.toLowerCase() + : `@${registry.scope.toLowerCase()}`; + + config.install.scopes[scopeName] = { + url: registry.url, + ...(registry.token ? { token: registry.token } : {}), + }; + } } - writeFileSync(path, newContent, { - encoding: "utf8", - }); + if (Object.keys(config.install.scopes).length === 0) { + delete config.install.scopes; + } + + writeFileSync(path, stringify(config), { encoding: "utf8" }); } diff --git a/tests/bunfig.spec.ts b/tests/bunfig.spec.ts index 05f64b6..81adcf6 100644 --- a/tests/bunfig.spec.ts +++ b/tests/bunfig.spec.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it } from "bun:test"; -import { unlinkSync } from "node:fs"; +import { existsSync, unlinkSync } from "node:fs"; import { writeBunfig } from "../src/bunfig"; import { EOL } from "os"; @@ -14,7 +14,7 @@ describe("writeBunfig", () => { } afterEach(() => { - unlinkSync(filePath); + if (existsSync(filePath)) unlinkSync(filePath); console.log(`${filePath} was deleted`); }); @@ -33,8 +33,9 @@ describe("writeBunfig", () => { expect(file.exists()).resolves.toBeTrue(); const expectedContents = [ - "[install.scopes]", - '\'@foo-bar\' = { url = "https://npm.pkg.github.com/", token = "$BUN_AUTH_TOKEN" }', + '[install.scopes."@foo-bar"]', + 'url = "https://npm.pkg.github.com"', + 'token = "$BUN_AUTH_TOKEN"', "", ]; @@ -46,6 +47,31 @@ describe("writeBunfig", () => { }); it("should create a new file with global registry", async () => { + writeBunfig(filePath, [ + { + url: "https://npm.pkg.github.com", + scope: "", + }, + ]); + + const { file, contents } = await getFileAndContents(); + + expect(file.exists()).resolves.toBeTrue(); + + const expectedContents = [ + "[install.registry]", + 'url = "https://npm.pkg.github.com"', + "", + ]; + + contents.forEach((content, index) => + expect(content).toBe(expectedContents[index]) + ); + + expect(contents.length).toBe(expectedContents.length); + }); + + it("should create a new file with global registry & token", async () => { writeBunfig(filePath, [ { url: "https://npm.pkg.github.com", @@ -59,8 +85,9 @@ describe("writeBunfig", () => { expect(file.exists()).resolves.toBeTrue(); const expectedContents = [ - "[install]", - 'registry = "https://npm.pkg.github.com/"', + "[install.registry]", + 'url = "https://npm.pkg.github.com"', + 'token = "$BUN_AUTH_TOKEN"', "", ]; @@ -94,10 +121,12 @@ describe("writeBunfig", () => { "[install]", "optional = true", "", - "[install.cache]", - "disable = true", - "[install.scopes]", - '\'@foo-bar\' = { url = "https://npm.pkg.github.com/", token = "$BUN_AUTH_TOKEN" }', + " [install.cache]", + " disable = true", + "", + '[install.scopes."@foo-bar"]', + 'url = "https://npm.pkg.github.com"', + 'token = "$BUN_AUTH_TOKEN"', "", ]; @@ -129,12 +158,16 @@ describe("writeBunfig", () => { "[install]", "optional = true", "", - "[install.scopes]", - '\'@foo-bar\' = { url = "https://npm.pkg.github.com/", token = "$BUN_AUTH_TOKEN" }', - '\'@bla-ble\' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }', + '[install.scopes."@bla-ble"]', + 'token = "$BUN_AUTH_TOKEN"', + 'url = "https://npm.pkg.github.com/"', "", - "[install.cache]", - "disable = true", + '[install.scopes."@foo-bar"]', + 'url = "https://npm.pkg.github.com"', + 'token = "$BUN_AUTH_TOKEN"', + "", + " [install.cache]", + " disable = true", "", ]; @@ -166,12 +199,16 @@ describe("writeBunfig", () => { "[install]", "optional = true", "", - "[install.scopes]", - '\'@foo-bar\' = { url = "https://npm.pkg.github.com/", token = "$BUN_AUTH_TOKEN" }', - '\'@bla-ble\' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }', + '[install.scopes."@foo-bar"]', + 'url = "https://npm.pkg.github.com"', + 'token = "$BUN_AUTH_TOKEN"', "", - "[install.cache]", - "disable = true", + '[install.scopes."@bla-ble"]', + 'token = "$BUN_AUTH_TOKEN"', + 'url = "https://npm.pkg.github.com/"', + "", + " [install.cache]", + " disable = true", "", ]; @@ -206,15 +243,22 @@ describe("writeBunfig", () => { const expectedContents = [ "[install]", - 'registry = "https://bun.sh/"', "optional = true", "", - "[install.scopes]", - '\'@foo-bar\' = { url = "https://npm.pkg.github.com/", token = "$BUN_AUTH_TOKEN" }', - '\'@bla-ble\' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }', + '[install.scopes."@foo-bar"]', + 'url = "https://npm.pkg.github.com"', + 'token = "$BUN_AUTH_TOKEN"', "", - "[install.cache]", - "disable = true", + '[install.scopes."@bla-ble"]', + 'token = "$BUN_AUTH_TOKEN"', + 'url = "https://npm.pkg.github.com/"', + "", + " [install.cache]", + " disable = true", + "", + " [install.registry]", + ' url = "https://bun.sh"', + ' token = "$BUN_AUTH_TOKEN"', "", ]; @@ -225,4 +269,23 @@ describe("writeBunfig", () => { expect(contents.length).toBe(expectedContents.length); }); }); + + describe("when multiple global registries are provided", () => { + it("should throw an error", () => { + expect(() => { + writeBunfig(filePath, [ + { + url: "https://npm.pkg.github.com", + scope: "", + token: "$BUN_AUTH_TOKEN", + }, + { + url: "https://bun.sh", + scope: "", + token: "$BUN_AUTH_TOKEN", + }, + ]); + }).toThrow("You can't have more than one global registry."); + }); + }); });