diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70f3393..6390cfc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -191,12 +191,10 @@ jobs: id: setup_bun with: registries: | - [ - { - "url": "https://username:password@registry.myorg.com/", - "scope": "@myorg" - } - ] + https://registry.npmjs.org/ + @myorg:https://username:password@registry.myorg.com/ + @another:https://registry.another.com/ + @partner:https://registry.partner.com/|basic_token - name: Run Bun id: run_bun diff --git a/bunfig.toml b/bunfig.toml deleted file mode 100644 index 7868d6b..0000000 --- a/bunfig.toml +++ /dev/null @@ -1,2 +0,0 @@ -[install] -saveTextLockfile = true diff --git a/src/bunfig.ts b/src/bunfig.ts index 522eeb6..cfa1158 100644 --- a/src/bunfig.ts +++ b/src/bunfig.ts @@ -1,12 +1,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { info } from "@actions/core"; import { parse, stringify } from "@iarna/toml"; - -export type Registry = { - url: string; - scope: string; - token?: string; -}; +import { Registry } from "./registry"; type BunfigConfig = { install?: { diff --git a/src/index.ts b/src/index.ts index 72b9188..716755d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,12 +2,13 @@ import { tmpdir } from "node:os"; import { getInput, setOutput, setFailed, getBooleanInput } from "@actions/core"; import runAction from "./action.js"; import { readVersionFromFile } from "./utils.js"; +import { parseRegistries } from "./registry.js"; if (!process.env.RUNNER_TEMP) { process.env.RUNNER_TEMP = tmpdir(); } -const registries = JSON.parse(getInput("registries") || "[]"); +const registries = parseRegistries(getInput("registries")); const registryUrl = getInput("registry-url"); const scope = getInput("scope"); diff --git a/src/registry.ts b/src/registry.ts new file mode 100644 index 0000000..af711d8 --- /dev/null +++ b/src/registry.ts @@ -0,0 +1,62 @@ +export type Registry = { + url: string; + scope: string; + token?: string; +}; + +/** + * Parse registries from the simplified format: + * - Default registry: https://registry.npmjs.org/ + * - Default registry with token: https://registry.npmjs.org/|token123 + * - With scope and credentials in URL: @myorg:https://username:password@registry.myorg.com/ + * - With scope and separate token: @partner:https://registry.partner.com/|basic_token + */ +export function parseRegistries(input: string): Registry[] { + if (!input?.trim()) return []; + + return input + .split("\n") + .map((line) => line.trim()) + .filter(Boolean) + .map(parseLine) + .filter(Boolean) as Registry[]; +} + +function parseLine(line: string): Registry | null { + const scopeMatch = line.match( + /^(@[a-z0-9-_.]+|[a-z0-9-_.]+(?=:[a-z]+:\/\/)):(.+)$/i + ); + + if (scopeMatch) { + const scope = scopeMatch[1]; + const urlPart = scopeMatch[2].trim(); + + const [url, token] = urlPart.split("|", 2).map((p) => p?.trim()); + + try { + new URL(url); + + return { + url, + scope, + ...(token && { token }), + }; + } catch (e) { + throw new Error(`Invalid URL in registry configuration: ${url}`); + } + } else { + const [url, token] = line.split("|", 2).map((p) => p?.trim()); + + try { + new URL(url); + + return { + url, + scope: "", + ...(token && { token }), + }; + } catch (e) { + throw new Error(`Invalid URL in registry configuration: ${url}`); + } + } +} diff --git a/tests/registry.spec.ts b/tests/registry.spec.ts new file mode 100644 index 0000000..46acdb8 --- /dev/null +++ b/tests/registry.spec.ts @@ -0,0 +1,170 @@ +import { describe, expect, it } from "bun:test"; +import { parseRegistries } from "../src/registry"; + +describe("registry", () => { + describe("parseRegistries", () => { + it("should return an empty array for empty input", () => { + expect(parseRegistries("")).toEqual([]); + expect(parseRegistries(" ")).toEqual([]); + expect(parseRegistries(null as any)).toEqual([]); + expect(parseRegistries(undefined as any)).toEqual([]); + }); + + it("should parse default registry without token", () => { + const input = "https://registry.npmjs.org/"; + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.npmjs.org/", + scope: "", + }, + ]); + }); + + it("should parse default registry with token", () => { + const input = "https://registry.npmjs.org/|npm_token123"; + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.npmjs.org/", + scope: "", + token: "npm_token123", + }, + ]); + }); + + it("should parse scoped registry with URL credentials", () => { + const input = "@myorg:https://username:password@registry.myorg.com/"; + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://username:password@registry.myorg.com/", + scope: "@myorg", + }, + ]); + }); + + it("should parse scoped registry with separate token", () => { + const input = "@partner:https://registry.partner.com/|token_abc123"; + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.partner.com/", + scope: "@partner", + token: "token_abc123", + }, + ]); + }); + + it("should parse multiple registries", () => { + const input = ` + https://registry.npmjs.org/ + @myorg:https://username:password@registry.myorg.com/ + @partner:https://registry.partner.com/|token_abc123 + `; + + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.npmjs.org/", + scope: "", + }, + { + url: "https://username:password@registry.myorg.com/", + scope: "@myorg", + }, + { + url: "https://registry.partner.com/", + scope: "@partner", + token: "token_abc123", + }, + ]); + }); + + it("should handle scope names without @ prefix", () => { + const input = "myorg:https://registry.myorg.com/|token123"; + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.myorg.com/", + scope: "myorg", + token: "token123", + }, + ]); + }); + + it("should throw error for invalid URLs", () => { + expect(() => { + parseRegistries("@myorg:not-a-valid-url"); + }).toThrow("Invalid URL in registry configuration: not-a-valid-url"); + + expect(() => { + parseRegistries("also-not-a-valid-url"); + }).toThrow("Invalid URL in registry configuration: also-not-a-valid-url"); + }); + + it("should handle whitespace and empty lines", () => { + const input = ` + + https://registry.npmjs.org/ + + @myorg:https://registry.myorg.com/|token123 + + `; + + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.npmjs.org/", + scope: "", + }, + { + url: "https://registry.myorg.com/", + scope: "@myorg", + token: "token123", + }, + ]); + }); + + it("should handle URLs with paths and query parameters", () => { + const input = + "@myorg:https://registry.myorg.com/npm/registry/?timeout=5000|token123"; + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.myorg.com/npm/registry/?timeout=5000", + scope: "@myorg", + token: "token123", + }, + ]); + }); + + it("should correctly handle scopes with hyphens and underscores", () => { + const input = ` + @my-org:https://registry.myorg.com/ + @my_org:https://registry.myorg.com/ + `; + + const result = parseRegistries(input); + + expect(result).toEqual([ + { + url: "https://registry.myorg.com/", + scope: "@my-org", + }, + { + url: "https://registry.myorg.com/", + scope: "@my_org", + }, + ]); + }); + }); +});