import { write } from "bun"; import { afterAll, beforeAll, describe, expect, it, test } from "bun:test"; import { rm } from "fs/promises"; import { VerdaccioRegistry, bunExe, bunEnv as env, stderrForInstall } from "harness"; import { join } from "path"; const { iniInternals } = require("bun:internal-for-testing"); const { loadNpmrc } = iniInternals; var registry = new VerdaccioRegistry(); beforeAll(async () => { await registry.start(); }); afterAll(() => { registry.stop(); }); describe("npmrc", async () => { const isBase64Encoded = (opt: string) => opt === "_auth" || opt === "_password"; it("should convert to utf8 if BOM", async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Promise.all([ write(join(packageDir, ".npmrc"), Buffer.from(`\ufeff\ncache=hi!`, "utf16le")), write(packageJson, JSON.stringify({ name: "foo", version: "1.0.0" })), rm(join(packageDir, "bunfig.toml"), { force: true }), ]); const originalCacheDir = env.BUN_INSTALL_CACHE_DIR; delete env.BUN_INSTALL_CACHE_DIR; const { stdout, stderr, exited } = Bun.spawn({ cmd: [bunExe(), "pm", "cache"], cwd: packageDir, env, stdout: "pipe", stderr: "pipe", }); env.BUN_INSTALL_CACHE_DIR = originalCacheDir; const out = await stdout.text(); const err = stderrForInstall(await stderr.text()); console.log({ out, err }); expect(err).toBeEmpty(); expect(out.endsWith("hi!")).toBeTrue(); expect(await exited).toBe(0); }); it("works with empty file", async () => { const { packageDir, packageJson } = await registry.createTestDir(); console.log("package dir", packageDir); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const ini = /* ini */ ``; await Bun.$`echo ${ini} > ${packageDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "foo", dependencies: {}, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install`.cwd(packageDir).throws(true); }); it("sets default registry", async () => { const { packageDir, packageJson } = await registry.createTestDir(); console.log("package dir", packageDir); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const ini = /* ini */ ` registry = http://localhost:${registry.port}/ `; await Bun.$`echo ${ini} > ${packageDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "foo", dependencies: { "no-deps": "1.0.0", }, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install`.cwd(packageDir).throws(true); }); it("sets scoped registry", async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const ini = /* ini */ ` @types:registry=http://localhost:${registry.port}/ `; await Bun.$`echo ${ini} > ${packageDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "foo", dependencies: { "@types/no-deps": "1.0.0", }, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install`.cwd(packageDir).throws(true); }); it("works with home config", async () => { const { packageDir, packageJson } = await registry.createTestDir(); console.log("package dir", packageDir); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const homeDir = `${packageDir}/home_dir`; await Bun.$`mkdir -p ${homeDir}`; console.log("home dir", homeDir); const ini = /* ini */ ` registry=http://localhost:${registry.port}/ `; await Bun.$`echo ${ini} > ${homeDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "foo", dependencies: { "no-deps": "1.0.0", }, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install` .env({ ...process.env, XDG_CONFIG_HOME: `${homeDir}`, }) .cwd(packageDir) .throws(true); }); it("works with two configs", async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; console.log("package dir", packageDir); const packageIni = /* ini */ ` @types:registry=http://localhost:${registry.port}/ `; await Bun.$`echo ${packageIni} > ${packageDir}/.npmrc`; const homeDir = `${packageDir}/home_dir`; await Bun.$`mkdir -p ${homeDir}`; console.log("home dir", homeDir); const homeIni = /* ini */ ` registry = http://localhost:${registry.port}/ `; await Bun.$`echo ${homeIni} > ${homeDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "foo", dependencies: { "no-deps": "1.0.0", "@types/no-deps": "1.0.0", }, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install` .env({ ...process.env, XDG_CONFIG_HOME: `${homeDir}`, }) .cwd(packageDir) .throws(true); }); it("package config overrides home config", async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; console.log("package dir", packageDir); const packageIni = /* ini */ ` @types:registry=http://localhost:${registry.port}/ `; await Bun.$`echo ${packageIni} > ${packageDir}/.npmrc`; const homeDir = `${packageDir}/home_dir`; await Bun.$`mkdir -p ${homeDir}`; console.log("home dir", homeDir); const homeIni = /* ini */ "@types:registry=https://registry.npmjs.org/"; await Bun.$`echo ${homeIni} > ${homeDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "foo", dependencies: { "@types/no-deps": "1.0.0", }, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install` .env({ ...process.env, XDG_CONFIG_HOME: `${homeDir}`, }) .cwd(packageDir) .throws(true); }); it("default registry from env variable", async () => { const { packageDir, packageJson } = await registry.createTestDir(); const ini = /* ini */ ` registry=\${LOL} `; const result = loadNpmrc(ini, { LOL: `http://localhost:${registry.port}/` }); expect(result.default_registry_url).toBe(`http://localhost:${registry.port}/`); }); it("default registry from env variable 2", async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const ini = /* ini */ ` registry=http://localhost:\${PORT}/ `; const result = loadNpmrc(ini, { ...env, PORT: registry.port }); expect(result.default_registry_url).toEqual(`http://localhost:${registry.port}/`); }); async function makeTest( options: [option: string, value: string][], check: (result: { default_registry_url: string; default_registry_token: string; default_registry_username: string; default_registry_password: string; default_registry_email: string; }) => void, ) { const optionName = await Promise.all(options.map(async ([name, val]) => `${name} = ${val}`)); test(optionName.join(" "), async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const iniInner = await Promise.all( options.map(async ([option, value]) => { let finalValue = value; finalValue = isBase64Encoded(option) ? Buffer.from(finalValue).toString("base64") : finalValue; return `//registry.npmjs.org/:${option}=${finalValue}`; }), ); const ini = /* ini */ ` ${iniInner.join("\n")} `; await Bun.$`echo ${JSON.stringify({ name: "hello", main: "index.js", version: "1.0.0", dependencies: { "is-even": "1.0.0", }, })} > package.json`.cwd(packageDir); await Bun.$`echo ${ini} > ${packageDir}/.npmrc`; const result = loadNpmrc(ini); check(result); }); } await makeTest([["_authToken", "skibidi"]], result => { expect(result.default_registry_url).toEqual("https://registry.npmjs.org/"); expect(result.default_registry_token).toEqual("skibidi"); }); await makeTest( [ ["username", "zorp"], ["_password", "skibidi"], ], result => { expect(result.default_registry_url).toEqual("https://registry.npmjs.org/"); expect(result.default_registry_username).toEqual("zorp"); expect(result.default_registry_password).toEqual("skibidi"); }, ); it("authentication works", async () => { const { packageDir, packageJson } = await registry.createTestDir(); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const ini = /* ini */ ` registry = http://localhost:${registry.port}/ @needs-auth:registry=http://localhost:${registry.port}/ //localhost:${registry.port}/:_authToken=${await registry.generateUser("bilbo_swaggins", "verysecure")} `; await Bun.$`echo ${ini} > ${packageDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "hi", main: "index.js", version: "1.0.0", dependencies: { "no-deps": "1.0.0", "@needs-auth/test-pkg": "1.0.0", }, "publishConfig": { "registry": `http://localhost:${registry.port}`, }, })} > package.json`.cwd(packageDir); await Bun.$`${bunExe()} install`.env(env).cwd(packageDir).throws(true); }); type EnvMap = | Omit< { [key: string]: string; }, "dotEnv" > | { dotEnv?: Record }; function registryConfigOptionTest( name: string, _opts: Record | (() => Promise>), _env?: EnvMap | (() => Promise), check?: (stdout: string, stderr: string) => void, ) { it(`sets scoped registry option: ${name}`, async () => { const { packageDir, packageJson } = await registry.createTestDir(); console.log("PACKAGE DIR", packageDir); await Bun.$`rm -rf ${packageDir}/bunfig.toml`; const { dotEnv, ...restOfEnv } = _env ? typeof _env === "function" ? await _env() : _env : { dotEnv: undefined }; const opts = _opts ? (typeof _opts === "function" ? await _opts() : _opts) : {}; const dotEnvInner = dotEnv ? Object.entries(dotEnv) .map(([k, v]) => `${k}=${k.includes("SECRET_") ? Buffer.from(v).toString("base64") : v}`) .join("\n") : ""; const ini = ` registry = http://localhost:${registry.port}/ ${Object.keys(opts) .map( k => `//localhost:${registry.port}/:${k}=${isBase64Encoded(k) && !opts[k].includes("${") ? Buffer.from(opts[k]).toString("base64") : opts[k]}`, ) .join("\n")} `; if (dotEnvInner.length > 0) await Bun.$`echo ${dotEnvInner} > ${packageDir}/.env`; await Bun.$`echo ${ini} > ${packageDir}/.npmrc`; await Bun.$`echo ${JSON.stringify({ name: "hi", main: "index.js", version: "1.0.0", dependencies: { "@needs-auth/test-pkg": "1.0.0", }, "publishConfig": { "registry": `http://localhost:${registry.port}`, }, })} > package.json`.cwd(packageDir); const { stdout, stderr } = await Bun.$`${bunExe()} install` .env({ ...env, ...restOfEnv }) .cwd(packageDir) .throws(check === undefined); if (check) check(stdout.toString(), stderr.toString()); }); } registryConfigOptionTest("_authToken", async () => ({ "_authToken": await registry.generateUser("bilbo_baggins", "verysecure"), })); registryConfigOptionTest( "_authToken with env variable value", async () => ({ _authToken: "${SUPER_SECRET_TOKEN}" }), async () => ({ SUPER_SECRET_TOKEN: await registry.generateUser("bilbo_baggins420", "verysecure") }), ); registryConfigOptionTest("username and password", async () => { await registry.generateUser("gandalf429", "verysecure"); return { username: "gandalf429", _password: "verysecure" }; }); registryConfigOptionTest( "username and password with env variable password", async () => { await registry.generateUser("gandalf422", "verysecure"); return { username: "gandalf422", _password: "${SUPER_SECRET_PASSWORD}" }; }, { SUPER_SECRET_PASSWORD: Buffer.from("verysecure").toString("base64"), }, ); registryConfigOptionTest( "username and password with .env variable password", async () => { await registry.generateUser("gandalf421", "verysecure"); return { username: "gandalf421", _password: "${SUPER_SECRET_PASSWORD}" }; }, { dotEnv: { SUPER_SECRET_PASSWORD: "verysecure" }, }, ); registryConfigOptionTest("_auth", async () => { await registry.generateUser("linus", "verysecure"); const _auth = "linus:verysecure"; return { _auth }; }); registryConfigOptionTest( "_auth from .env variable", async () => { await registry.generateUser("zack", "verysecure"); return { _auth: "${SECRET_AUTH}" }; }, { dotEnv: { SECRET_AUTH: "zack:verysecure" }, }, ); registryConfigOptionTest( "_auth from .env variable with no value", async () => { await registry.generateUser("zack420", "verysecure"); return { _auth: "${SECRET_AUTH}" }; }, { dotEnv: { SECRET_AUTH: "" }, }, (stdout: string, stderr: string) => { expect(stderr).toContain("received an empty string"); }, ); await makeTest([["email", "user@example.com"]], result => { expect(result.default_registry_url).toEqual("https://registry.npmjs.org/"); expect(result.default_registry_email).toEqual("user@example.com"); }); await makeTest( [ ["username", "testuser"], ["_password", "testpass"], ["email", "test@example.com"], ], result => { expect(result.default_registry_url).toEqual("https://registry.npmjs.org/"); expect(result.default_registry_username).toEqual("testuser"); expect(result.default_registry_password).toEqual("testpass"); expect(result.default_registry_email).toEqual("test@example.com"); }, ); });