Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@octokit/plugin-throttling": "^11.0.3",
"human-id": "^4.1.3",
"markdown-table": "^3.0.4",
"package-manager-detector": "^1.6.0",
"semver": "^7.8.1",
"tinyexec": "^1.2.4"
},
Expand All @@ -50,5 +51,5 @@
"engines": {
"node": ">=24"
},
"packageManager": "pnpm@11.1.1+sha512.d1fdf5f73c617b64fa1a56a81c3c8dfe0e966e33a6010aa256b517ae77be21d93e05affc0de1a83b0e4f29d569f68b446ae8f068cd7247c0bb3df0fb4d7bdf9a"
"packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620"
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions rolldown.config.js → rolldown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { defineConfig } from "rolldown";
export default defineConfig({
input: {
index: "src/index.ts",
["pr-status"]: "src/pr-status/index.ts",
["pr-comment"]: "src/pr-comment/index.ts",
"pr-status": "src/pr-status/index.ts",
"pr-comment": "src/pr-comment/index.ts",
"setup-auth": "src/setup-auth/index.ts",
},
output: {
dir: "dist",
Expand Down
31 changes: 31 additions & 0 deletions setup-auth/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "changesets/setup-auth"
description: Set up authentication for Node.js projects, without also re-downloading Node.js (again).

branding:
icon: lock
color: blue

runs:
using: node24
main: ../dist/setup-auth.js

inputs:
token:
description: The token to write to the package managers' config files.
required: true
scope:
description: Which scope to use the token with (e.g. `@my-org`).
registry:
description: Which registry to use the token with.
default: "//registry.npmjs.org"
package-manager:
# TODO: fix wording
description: Which package manager to set up. Defaults to detecting from project. (see supported package managers).
overwrite:
description: Whether to overwrite any existing configuration.

outputs:
config-path:
description: The path to the updated config file
package-manager:
description: Which package manager was configured
22 changes: 22 additions & 0 deletions src/setup-auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as core from "@actions/core";
import { setupAuth } from "./setup.ts";

try {
await main();
} catch (err) {
core.setFailed((err as Error).message);
}

async function main() {
core.info("Setting up npm registry authentication...");
const { configPath, packageManager } = await setupAuth({
token: core.getInput("token", { required: true }),
registry: core.getInput("registry"),
scope: core.getInput("scope"),
packageManager: core.getInput("package-manager") as any,
overwrite: core.getInput("overwrite") === "true",
});
core.setOutput("config-path", configPath);
core.setOutput("package-manager", packageManager);
core.info("Done!");
}
191 changes: 191 additions & 0 deletions src/setup-auth/setup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeEach, describe, expect, it } from "vitest";
import { setupAuth, type SetupAuthInputs } from "./setup.ts";

describe("setup()", () => {
let originalHome = process.env.HOME;
let homeDir!: string;
beforeEach(async () => {
homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "setup-auth-"));
process.env.HOME = homeDir;
});
afterAll(() => {
process.env.HOME = originalHome;
});

it("should refuse if file already exists", async () => {
const filePath = path.join(homeDir, ".npmrc");
const originalFileContents = "registry=https://registry.npmjs.org";
await fs.writeFile(filePath, originalFileContents);

const inputs = {
token: "token",
registry: "//test.registry.com",
scope: undefined,
packageManager: "npm",
overwrite: false,
} satisfies SetupAuthInputs;

await expect(setupAuth(inputs)).rejects.toThrow("file already exists");

await expect(fs.readFile(filePath, "utf8")).resolves.toEqual(
originalFileContents,
);
});

it("should overwrite if configured to", async () => {
const filePath = path.join(homeDir, ".npmrc");
const originalFileContents = "registry=https://registry.npmjs.org";
await fs.writeFile(filePath, originalFileContents);

const inputs = {
token: "token",
registry: "//test.registry.com",
scope: undefined,
packageManager: "npm",
overwrite: true,
} satisfies SetupAuthInputs;

await expect(setupAuth(inputs)).resolves.toBeDefined();
await expect(fs.readFile(filePath, "utf8")).resolves.not.toEqual(
originalFileContents,
);
});

describe("pnpm", () => {
it("should configure token", async () => {
const inputs = {
token: "token",
registry: "//test.registry.com",
scope: undefined,
packageManager: "pnpm",
overwrite: false,
} satisfies SetupAuthInputs;
const { configPath, packageManager } = await setupAuth(inputs);

expect(packageManager).toBe("pnpm");
const expectedPath = path.join(homeDir, ".config", "pnpm", "auth.ini");
expect(configPath).toEqual(expectedPath);
await expect(
fs.readFile(expectedPath, "utf8"),
).resolves.toMatchInlineSnapshot(
`"//test.registry.com/:_authToken=token"`,
);
});

it("should configure token with scope", async () => {
const inputs = {
token: "token",
registry: "//test.registry.com",
scope: "@my-org",
packageManager: "pnpm",
overwrite: false,
} satisfies SetupAuthInputs;
const { configPath, packageManager } = await setupAuth(inputs);

expect(packageManager).toBe("pnpm");
const expectedPath = path.join(homeDir, ".config", "pnpm", "auth.ini");
expect(configPath).toEqual(expectedPath);
await expect(
fs.readFile(expectedPath, "utf8"),
).resolves.toMatchInlineSnapshot(
`"@my-org://test.registry.com/:_authToken=token"`,
);
});
});

describe("npm", () => {
it("should configure token", async () => {
const inputs = {
token: "token",
registry: "//test.registry.com",
scope: undefined,
packageManager: "npm",
overwrite: false,
} satisfies SetupAuthInputs;
const { configPath, packageManager } = await setupAuth(inputs);

expect(packageManager).toBe("npm");
const expectedPath = path.join(homeDir, ".npmrc");
expect(configPath).toEqual(expectedPath);
await expect(
fs.readFile(expectedPath, "utf8"),
).resolves.toMatchInlineSnapshot(
`"//test.registry.com/:_authToken=token"`,
);
});

it("should configure token with scope", async () => {
const inputs = {
token: "token",
registry: "//test.registry.com",
scope: "@my-org",
packageManager: "npm",
overwrite: false,
} satisfies SetupAuthInputs;
const { configPath, packageManager } = await setupAuth(inputs);

expect(packageManager).toBe("npm");
const expectedPath = path.join(homeDir, ".npmrc");
expect(configPath).toEqual(expectedPath);
await expect(
fs.readFile(expectedPath, "utf8"),
).resolves.toMatchInlineSnapshot(
`"@my-org://test.registry.com/:_authToken=token"`,
);
});
});

describe("yarn", () => {
it("should configure token", async () => {
const inputs = {
token: "token",
registry: "//test.registry.com",
scope: undefined,
packageManager: "yarn",
overwrite: false,
} satisfies SetupAuthInputs;
const { configPath, packageManager } = await setupAuth(inputs);

expect(packageManager).toBe("yarn");
const expectedPath = path.join(homeDir, ".yarnrc.yml");
expect(configPath).toEqual(expectedPath);
await expect(
fs.readFile(expectedPath, "utf8"),
).resolves.toMatchInlineSnapshot(
`
"npmRegistries:
//test.registry.com:
npmAuthToken: "token""
`,
);
});

it("should configure token with scope", async () => {
const inputs = {
token: "token",
registry: "//test.registry.com",
scope: "@my-org",
packageManager: "yarn",
overwrite: false,
} satisfies SetupAuthInputs;
const { configPath, packageManager } = await setupAuth(inputs);

expect(packageManager).toBe("yarn");
const expectedPath = path.join(homeDir, ".yarnrc.yml");
expect(configPath).toEqual(expectedPath);
await expect(
fs.readFile(expectedPath, "utf8"),
).resolves.toMatchInlineSnapshot(
`
"npmScopes:
my-org:
npmAuthToken: "token"
npmRegistryServer: "//test.registry.com""
`,
);
});
});
});
Loading