Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/smart-parks-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/action": minor
---

Internal auth setup for provided `NPM_TOKEN` environment variable when using Yarn gets now written to Yarn Berry's `~/.yarnrc.yml`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a big benefit from doing this compared to just using env vars?

if anything, storing the value in a file if we might not need to sounds like a bad idea

@Andarist Andarist Jun 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and I was under the impression that you kinda can't get away from storing it in .npmrc file. But, according to a fresh agent run, npm actually supports overlaying its configs with environment variables, so... this should work:

  let changesetPublishOutput = await getExecOutput(script, undefined, {
    cwd,
    ignoreReturnCode: true,
    env: {
      ...process.env,
      GITHUB_TOKEN: githubToken,
      ...(process.env.NPM_TOKEN && {
        "npm_config_//registry.npmjs.org/:_authToken": process.env.NPM_TOKEN,
      }),
    },
  });

I would have to test this out to ensure it works but this would be a great way of improving this (thanks for the pushback!).

That said:

  • pnpm doesn't seem to support this. It might support it in older versions (pre v11) because it just delegated to the npm CLI for a bunch of this stuff, but v11 reimplements publishing and it doesn't seem to support this
  • yarn classic supports YARN_AUTH_TOKEN and NPM_AUTH_TOKEN (yes, it supports an npm-named variable that npm itself doesn't support), and it also supports the weird npm_config_* shape too
  • yarn berry supports YARN_NPM_AUTH_TOKEN

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, so for yarn we can use env vars, but for pnpm and npm we need to write it to a file, i see.

pnpm still supports .npmrc for just auth, and it can also be put in ~/.config/pnpm/auth.ini or (i think) ~/.config/pnpm/rc

100 changes: 69 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import * as core from "@actions/core";
import { getPackages } from "@manypkg/get-packages";
import { Git } from "./git.ts";
import { setupOctokit } from "./octokit.ts";
import readChangesetState from "./readChangesetState.ts";
Expand All @@ -9,6 +10,72 @@ import { fileExists } from "./utils.ts";

const getOptionalInput = (name: string) => core.getInput(name) || undefined;

async function ensureUserNpmToken(tool: string, token: string) {
if (tool === "yarn") {
const userYarnrcPath = `${process.env.HOME}/.yarnrc.yml`;

if (await fileExists(userYarnrcPath)) {
core.info("Found existing user .yarnrc.yml file");
const userYarnrcContent = await fs.readFile(userYarnrcPath, "utf8");
const authLine = userYarnrcContent.split("\n").find((line) => {
// indented npmAuthToken can be found when configured for a specific registry in npmRegistries
// in here, we only take care of the default one (a top-level one)
return /^npmAuthToken\s*:/i.test(line);
});
if (authLine) {
core.info(
"Found existing npmAuthToken in the user .yarnrc.yml file",
);
} else {
core.info(
"Didn't find npmAuthToken in the user .yarnrc.yml file, creating one",
);
await fs.appendFile(userYarnrcPath, `\nnpmAuthToken: ${token}\n`);
}
} else {
core.info(
"No user .yarnrc.yml file found, creating one with NPM_TOKEN used as npmAuthToken",
);
await fs.writeFile(userYarnrcPath, `npmAuthToken: ${token}\n`);
}

return;
}

// pnpm still respects .npmrc for auth tokens but its own `pnpm login` writes to `~/.config/pnpm/auth.ini`
const userNpmrcPath = `${process.env.HOME}/.npmrc`;

if (await fileExists(userNpmrcPath)) {
core.info("Found existing user .npmrc file");
const userNpmrcContent = await fs.readFile(userNpmrcPath, "utf8");
const authLine = userNpmrcContent.split("\n").find((line) => {
// check based on https://github.com/npm/cli/blob/8f8f71e4dd5ee66b3b17888faad5a7bf6c657eed/test/lib/adduser.js#L103-L105
return /^\s*\/\/registry\.npmjs\.org\/:[_-]authToken=/i.test(line);
});
if (authLine) {
core.info(
"Found existing auth token for the npm registry in the user .npmrc file",
);
} else {
core.info(
"Didn't find existing auth token for the npm registry in the user .npmrc file, creating one",
);
await fs.appendFile(
userNpmrcPath,
`\n//registry.npmjs.org/:_authToken=${token}\n`,
);
}
} else {
core.info(
"No user .npmrc file found, creating one with NPM_TOKEN used as auth token",
);
await fs.writeFile(
userNpmrcPath,
`//registry.npmjs.org/:_authToken=${token}\n`,
);
}
}

(async () => {
// to maintain compatibility with workflows created before github-token input was introduced
// it's important to prefer the explicitly set GITHUB_TOKEN over the default token coming from github.token
Expand Down Expand Up @@ -76,37 +143,8 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
);

if (process.env.NPM_TOKEN) {
const userNpmrcPath = `${process.env.HOME}/.npmrc`;

if (await fileExists(userNpmrcPath)) {
core.info("Found existing user .npmrc file");
const userNpmrcContent = await fs.readFile(userNpmrcPath, "utf8");
const authLine = userNpmrcContent.split("\n").find((line) => {
// check based on https://github.com/npm/cli/blob/8f8f71e4dd5ee66b3b17888faad5a7bf6c657eed/test/lib/adduser.js#L103-L105
return /^\s*\/\/registry\.npmjs\.org\/:[_-]authToken=/i.test(line);
});
if (authLine) {
core.info(
"Found existing auth token for the npm registry in the user .npmrc file",
);
} else {
core.info(
"Didn't find existing auth token for the npm registry in the user .npmrc file, creating one",
);
await fs.appendFile(
userNpmrcPath,
`\n//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}\n`,
);
}
} else {
core.info(
"No user .npmrc file found, creating one with NPM_TOKEN used as auth token",
);
await fs.writeFile(
userNpmrcPath,
`//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}\n`,
);
}
let { tool } = await getPackages(cwd);
await ensureUserNpmToken(tool, process.env.NPM_TOKEN);
} else if (
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN &&
process.env.ACTIONS_ID_TOKEN_REQUEST_URL
Expand Down
Loading