diff --git a/bun.lockb b/bun.lockb index 9e5fbb0..6ede1d5 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index b52264f..e89be66 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "@actions/exec": "^1.1.1", "@actions/glob": "^0.4.0", "@actions/io": "^1.1.2", - "@actions/tool-cache": "^2.0.1" + "@actions/tool-cache": "^2.0.1", + "@iarna/toml": "^2.2.5" }, "devDependencies": { "@types/bun": "^1.1.13", diff --git a/src/bunfig.ts b/src/bunfig.ts index 8d964f7..522eeb6 100644 --- a/src/bunfig.ts +++ b/src/bunfig.ts @@ -1,6 +1,6 @@ -import { EOL } from "node:os"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { info } from "@actions/core"; +import { parse, stringify } from "@iarna/toml"; export type Registry = { url: string; @@ -8,164 +8,79 @@ export type Registry = { token?: string; }; -enum FieldType { - GLOBAL_REGISTRY, - INSTALL_WITH_SCOPE, -} - -type Field = { - type: FieldType; - value: string; +type BunfigConfig = { + install?: { + registry?: { + url: string; + token?: string; + }; + scopes?: Record; + }; + [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."); + }); + }); });