@anolilab/semantic-release-pnpm
Version:
Semantic-release plugin to publish a npm package with pnpm.
804 lines (775 loc) • 33.9 kB
JavaScript
import { createRequire as __cjs_createRequire } from "node:module";
const __cjs_require = __cjs_createRequire(import.meta.url);
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
const __cjs_getBuiltinModule = (module) => {
// Check if we're in Node.js and version supports getBuiltinModule
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
// Node.js 20.16.0+ and 22.3.0+
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
return __cjs_getProcess.getBuiltinModule(module);
}
}
// Fallback to createRequire
return __cjs_require(module);
};
import dbg from 'debug';
import { execa } from 'execa';
import { validRange, gte } from 'semver';
import { isAccessibleSync, ensureFileSync, move, writeFile } from '@visulima/fs';
import { resolve } from '@visulima/path';
import { rc } from '@anolilab/rc';
import normalizeUrl from 'normalize-url';
import { findPackageJson, getPackageManagerVersion } from '@visulima/package';
import SemanticReleaseError from '@semantic-release/error';
import { stringify } from 'ini';
import getAuthToken from 'registry-auth-token';
import { getIDToken } from '@actions/core';
import envCi from 'env-ci';
const {
URL
} = __cjs_getBuiltinModule("node:url");
const getChannel = (channel) => {
if (!channel) {
return "latest";
}
if (validRange(channel)) {
return `release-${channel}`;
}
return channel;
};
const getNpmrcPath = (cwd, environment) => {
const npmrcPath = resolve(cwd, ".npmrc");
if (environment.NPM_CONFIG_USERCONFIG && isAccessibleSync(environment.NPM_CONFIG_USERCONFIG)) {
return environment.NPM_CONFIG_USERCONFIG;
}
if (isAccessibleSync(npmrcPath)) {
return npmrcPath;
}
ensureFileSync(npmrcPath);
return npmrcPath;
};
const OFFICIAL_REGISTRY = "https://registry.npmjs.org/";
const GITHUB_ACTIONS_PROVIDER_NAME = "GitHub Actions";
const GITLAB_PIPELINES_PROVIDER_NAME = "GitLab CI/CD";
const debug$9 = dbg("semantic-release-pnpm:registry");
const getRegistryUrl = (scope, npmrc) => {
let url = OFFICIAL_REGISTRY;
if (npmrc) {
const registryUrl = npmrc[`${scope}:registry`] ?? npmrc.registry;
if (registryUrl) {
url = registryUrl;
}
}
return url.endsWith("/") ? url : `${url}/`;
};
const getRegistry = ({ name, publishConfig = {} }, { cwd, env }) => {
let resolvedRegistry;
let source;
const scope = name.split("/")[0];
const publishRegistry = publishConfig[`${scope}:registry`] ?? publishConfig.registry;
if (publishRegistry) {
resolvedRegistry = publishRegistry;
source = "package.json#publishConfig.registry";
} else if (env.NPM_CONFIG_REGISTRY) {
resolvedRegistry = env.NPM_CONFIG_REGISTRY;
source = "NPM_CONFIG_REGISTRY environment variable";
} else {
const npmrcConfig = rc("npm", {
config: env.NPM_CONFIG_USERCONFIG ?? resolve(cwd, ".npmrc"),
cwd,
defaults: { registry: OFFICIAL_REGISTRY }
});
const npmrc = npmrcConfig.config;
resolvedRegistry = getRegistryUrl(scope, npmrc);
if (npmrc && (npmrc[`${scope}:registry`] ?? npmrc.registry)) {
source = `.npmrc file (${npmrc[`${scope}:registry`] ? `scoped registry for ${scope}` : "default registry"})`;
} else {
source = "default registry (OFFICIAL_REGISTRY)";
}
}
const finalRegistry = resolvedRegistry.endsWith("/") ? resolvedRegistry : `${resolvedRegistry}/`;
debug$9(`Resolved registry "${finalRegistry}" from ${source}`);
return finalRegistry;
};
const getReleaseInfo = ({ name }, { env: { DEFAULT_NPM_REGISTRY = OFFICIAL_REGISTRY }, nextRelease: { version } }, distributionTag, registry) => {
return {
channel: distributionTag,
name: `pnpm package (@${distributionTag} dist-tag)`,
url: normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY) ? `https://www.npmjs.com/package/${name}/v/${version}` : void 0
};
};
const reasonToNotPublish = (pluginConfig, package_) => {
if (pluginConfig.npmPublish === false) {
return "npmPublish plugin option is false";
}
if (package_.private === true && package_.workspaces === void 0) {
return "package is private and has no workspaces";
}
return null;
};
const shouldPublish = (pluginConfig, package_) => reasonToNotPublish(pluginConfig, package_) === null;
const debug$8 = dbg("semantic-release-pnpm:add-channel");
const addChannel$1 = async (pluginConfig, packageJson, context) => {
const {
cwd,
env,
logger,
nextRelease: { channel, version },
stderr,
stdout
} = context;
if (shouldPublish(pluginConfig, packageJson)) {
const registry = getRegistry(packageJson, context);
const distributionTag = getChannel(channel);
logger.log(`Adding version ${version} to npm registry on dist-tag ${distributionTag}`);
const npmrc = getNpmrcPath(cwd, env);
const distTagArguments = ["dist-tag", "add", `${packageJson.name}@${version}`, distributionTag, "--registry", registry];
debug$8(`Executing: pnpm ${distTagArguments.join(" ")}`);
const result = execa("pnpm", distTagArguments, {
cwd,
env: {
...env,
NPM_CONFIG_USERCONFIG: npmrc
},
preferLocal: true
});
result.stdout.pipe(stdout, { end: false });
result.stderr.pipe(stderr, { end: false });
await result;
logger.log(`Added ${packageJson.name}@${version} to dist-tag @${distributionTag} on ${registry}`);
return getReleaseInfo(packageJson, context, distributionTag, registry);
}
logger.log(`Skip adding to npm channel as ${reasonToNotPublish(pluginConfig, packageJson)}`);
return false;
};
const debug$7 = dbg("semantic-release-pnpm:prepare");
const prepare$1 = async ({ pkgRoot, tarballDir }, { cwd, env, logger, nextRelease: { version }, stderr, stdout }) => {
const basePath = pkgRoot ? resolve(cwd, pkgRoot) : cwd;
logger.log("Write version %s to package.json in %s", version, basePath);
const versionResult = execa("pnpm", ["version", version, "--no-git-tag-version", "--allow-same-version"], {
cwd: basePath,
env,
preferLocal: true
});
versionResult.stdout.pipe(stdout, { end: false });
versionResult.stderr.pipe(stderr, { end: false });
await versionResult;
if (tarballDir) {
logger.log("Creating npm package version %s", version);
const packResult = execa("pnpm", ["pack", basePath], { cwd, env, preferLocal: true });
packResult.stdout.pipe(stdout, { end: false });
packResult.stderr.pipe(stderr, { end: false });
const tarball = (await packResult).stdout.split("\n").pop();
const tarballSource = resolve(cwd, tarball);
const tarballDestination = resolve(cwd, tarballDir.trim(), tarball);
debug$7(`Created tarball: ${tarball}`);
if (tarballSource === tarballDestination) {
debug$7(`Tarball already at destination: ${tarballDestination}`);
} else {
debug$7(`Moving tarball from ${tarballSource} to ${tarballDestination}`);
await move(tarballSource, tarballDestination);
}
}
};
const debug$6 = dbg("semantic-release-pnpm:publish");
const publish$1 = async (pluginConfig, packageJson, context) => {
const {
cwd,
env,
logger,
nextRelease: { channel, version },
stderr,
stdout
} = context;
const { pkgRoot, publishBranch: publishBranchConfig } = pluginConfig;
if (shouldPublish(pluginConfig, packageJson)) {
const basePath = pkgRoot ? resolve(cwd, pkgRoot) : cwd;
const registry = getRegistry(packageJson, context);
const distributionTag = getChannel(channel);
const { stdout: currentBranch } = await execa("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
cwd,
env,
preferLocal: true
});
const publishBranches = typeof publishBranchConfig === "string" && publishBranchConfig.split("|");
const isPublishBranch = publishBranches && publishBranches.includes(currentBranch);
const publishBranch = isPublishBranch ? currentBranch : "main";
logger.log(`Publishing version ${version} on branch ${publishBranch} to npm registry on dist-tag ${distributionTag}`);
const pnpmArguments = ["publish", basePath, "--publish-branch", publishBranch, "--tag", distributionTag, "--registry", registry, "--no-git-checks"];
if (pluginConfig.disableScripts) {
pnpmArguments.push("--ignore-scripts");
}
debug$6(`Executing: pnpm ${pnpmArguments.join(" ")}`);
const result = execa("pnpm", pnpmArguments, {
cwd,
env,
preferLocal: true
});
result.stdout.pipe(stdout, { end: false });
result.stderr.pipe(stderr, { end: false });
try {
await result;
} catch (error) {
logger.log(`Failed to publish ${packageJson.name}@${version} to dist-tag @${distributionTag} on ${registry}: ${error.message ?? error}`);
throw new AggregateError([error], error.message);
}
logger.log(`Published ${packageJson.name}@${version} to dist-tag @${distributionTag} on ${registry}`);
return getReleaseInfo(packageJson, context, distributionTag, registry);
}
logger.log(`Skip publishing to npm registry as ${reasonToNotPublish(pluginConfig, packageJson)}`);
return false;
};
var homepage = "https://github.com/anolilab/semantic-release/tree/main/packages/semantic-release-pnpm";
const packageJson = {
homepage: homepage};
const linkify = (file) => `${packageJson.homepage}/blob/main/${file}`;
const errors = {
EINVALIDBRANCHES: (branches) => {
return {
details: `The [branches option](${linkify("README.md#branches")}) option, if defined, must be an array of \`String\`.
Your configuration for the \`branches\` option is \`${branches.join(",")}\`.`,
message: "Invalid `branches` option."
};
},
EINVALIDNPMAUTH: ({ registry }) => {
return {
details: `The [authentication required to publish](${linkify(
"README.md#npm-registry-authentication"
)}) configured in the \`NPM_TOKEN\` environment variable must be a valid [token](https://docs.npmjs.com/getting-started/working_with_tokens) allowed to publish to the registry \`${String(registry)}\`.
Please make sure to set the \`NPM_TOKEN\` environment variable in your CI with the exact value of the npm token.`,
message: "Invalid npm authentication."
};
},
EINVALIDNPMPUBLISH: ({ npmPublish }) => {
return {
details: `The [npmPublish option](${linkify("README.md#npmpublish")}) option, if defined, must be a \`Boolean\`.
Your configuration for the \`npmPublish\` option is \`${String(npmPublish)}\`.`,
message: "Invalid `npmPublish` option."
};
},
EINVALIDNPMTOKEN: ({ registry }) => {
return {
details: `The [npm token](${linkify(
"README.md#npm-registry-authentication"
)}) configured in the \`NPM_TOKEN\` environment variable must be a valid [token](https://docs.npmjs.com/getting-started/working_with_tokens) allowing to publish to the registry \`${String(registry)}\`.
Please verify your authentication configuration.`,
message: "Invalid npm token."
};
},
EINVALIDPKGROOT: ({ pkgRoot }) => {
return {
details: `The [pkgRoot option](${linkify("README.md#pkgroot")}) option, if defined, must be a \`String\`.
Your configuration for the \`pkgRoot\` option is \`${String(pkgRoot)}\`.`,
message: "Invalid `pkgRoot` option."
};
},
EINVALIDPNPM: ({ version }) => {
return {
details: `The version of Pnpm that you are using is not compatible. Please refer to [the README](${linkify(
"README.md#install"
)}) to review which versions of Pnpm are currently supported
Your version of Pnpm is "${String(version)}".`,
message: "Incompatible Pnpm version detected."
};
},
EINVALIDPUBLISHBRANCH: ({ publishBranch }) => {
return {
details: `The [publishBranch option](${linkify("README.md#publishBranch")}) option, if defined, must be a \`String\`.
Your configuration for the \`publishBranch\` option is \`${String(publishBranch)}\`.`,
message: "Invalid `publishBranch` option."
};
},
EINVALIDTARBALLDIR: ({ tarballDir }) => {
return {
details: `The [tarballDir option](${linkify("README.md#tarballdir")}) option, if defined, must be a \`String\`.
Your configuration for the \`tarballDir\` option is \`${String(tarballDir)}\`.`,
message: "Invalid `tarballDir` option."
};
},
ENONPMTOKEN: ({ registry }) => {
return {
details: `When not publishing through [trusted publishing](https://docs.npmjs.com/trusted-publishers), an [npm token](${linkify(
"README.md#npm-registry-authentication"
)}) must be created and set in the \`NPM_TOKEN\` environment variable on your CI environment.
Please make sure to create an [npm token](https://docs.npmjs.com/getting-started/working_with_tokens#how-to-create-new-tokens) and set it in the \`NPM_TOKEN\` environment variable on your CI environment. The token must allow publishing to the registry \`${registry}\`.`,
message: "No npm token specified."
};
},
ENOPKG: () => {
return {
details: `A [package.json file](https://docs.npmjs.com/files/package.json) at the root of your project is required to release on npm.
Please follow the [npm guideline](https://docs.npmjs.com/getting-started/creating-node-modules) to create a valid \`package.json\` file.`,
message: "Missing `package.json` file."
};
},
ENOPKGNAME: () => {
return {
details: `The \`package.json\`'s [name](https://docs.npmjs.com/files/package.json#name) property is required in order to publish a package to the npm registry.
Please make sure to add a valid \`name\` for your package in your \`package.json\`.`,
message: "Missing `name` property in `package.json`."
};
},
ENOPNPM: () => {
return {
details: `The Pnpm CLI could not be found in your PATH. Make sure Pnpm is installed and try again.`,
message: "Pnpm not found."
};
},
ENOPNPMRC: () => {
return {
details: `Didnt find a \`.npmrc\` file or it was not possible to create , in the root of your project.`,
message: "Missing `.npmrc` file."
};
}
};
const getError = (code, context = {}) => {
const { details, message } = errors[code](context);
return new SemanticReleaseError(message, code, details);
};
const getPackage = async ({ pkgRoot }, { cwd }) => {
try {
const { packageJson } = await findPackageJson(pkgRoot ? resolve(cwd, pkgRoot) : cwd);
if (!packageJson.name) {
const semanticError = getError("ENOPKGNAME");
throw new AggregateError([semanticError], semanticError.message);
}
return packageJson;
} catch (error) {
if (error.code === "ENOENT") {
const semanticError = getError("ENOPKG");
throw new AggregateError([semanticError], semanticError.message);
}
throw error;
}
};
const debug$5 = dbg("semantic-release-pnpm:token-exchange");
const exchangeIdToken = async (idToken, packageName, context) => {
const response = await fetch(`${OFFICIAL_REGISTRY}-/npm/v1/oidc/token/exchange/package/${encodeURIComponent(packageName)}`, {
headers: { Authorization: `Bearer ${idToken}` },
method: "POST"
});
const responseBody = await response.json();
if (response.ok) {
debug$5("OIDC token exchange with the npm registry succeeded");
return responseBody.token;
}
debug$5(`OIDC token exchange with the npm registry failed: ${response.status} ${responseBody.message}`);
if (response.status === 404 || responseBody.message?.toLowerCase().includes("not found")) {
const warningMessage = `Package "${packageName}" does not exist on npm. npm requires a package to exist before you can configure OIDC trusted publishing. You can either publish a dummy version manually first (e.g., \`pnpm publish --tag dummy\`) or use the \`setup-npm-trusted-publish\` tool (https://github.com/azu/setup-npm-trusted-publish) to create a placeholder package. After the package exists, configure OIDC trusted publishing at https://www.npmjs.com/package/${encodeURIComponent(packageName)}/access`;
context?.logger?.error(warningMessage);
debug$5(warningMessage);
}
return void 0;
};
const exchangeGithubActionsToken = async (packageName, context) => {
let idToken;
debug$5("Verifying OIDC context for publishing from GitHub Actions");
try {
idToken = await getIDToken("npm:registry.npmjs.org");
} catch (error) {
debug$5(`Retrieval of GitHub Actions OIDC token failed: ${error.message}`);
debug$5("Have you granted the `id-token: write` permission to this workflow?");
return void 0;
}
if (!idToken) {
debug$5("GitHub Actions OIDC token is not available");
debug$5("Have you granted the `id-token: write` permission to this workflow?");
return void 0;
}
return exchangeIdToken(idToken, packageName, context);
};
const exchangeGitlabPipelinesToken = async (packageName, context) => {
const idToken = process.env.NPM_ID_TOKEN;
debug$5("Verifying OIDC context for publishing from GitLab Pipelines");
if (!idToken) {
debug$5("NPM_ID_TOKEN environment variable is not set");
debug$5("Configure trusted publishing in your GitLab project settings and set the NPM_ID_TOKEN variable");
return void 0;
}
return exchangeIdToken(idToken, packageName, context);
};
const tokenExchange = (pkg, context) => {
if (!pkg.name || typeof pkg.name !== "string" || pkg.name.trim() === "") {
context.logger.error("Invalid package name provided for OIDC token exchange");
return Promise.resolve(void 0);
}
const ciEnv = envCi();
const ciProviderName = typeof ciEnv === "object" && ciEnv !== null && typeof ciEnv.name === "string" ? ciEnv.name : void 0;
if (!ciProviderName) {
debug$5("Unable to detect CI provider for OIDC token exchange");
debug$5("Supported CI providers for OIDC trusted publishing: GitHub Actions, GitLab CI/CD");
return Promise.resolve(void 0);
}
debug$5(`Detected CI provider: ${ciProviderName}`);
if (GITHUB_ACTIONS_PROVIDER_NAME === ciProviderName) {
return exchangeGithubActionsToken(pkg.name, context);
}
if (GITLAB_PIPELINES_PROVIDER_NAME === ciProviderName) {
return exchangeGitlabPipelinesToken(pkg.name, context);
}
debug$5(`CI provider "${ciProviderName}" is not supported for OIDC trusted publishing`);
debug$5("Supported CI providers: GitHub Actions, GitLab CI/CD");
return Promise.resolve(void 0);
};
const debug$4 = dbg("semantic-release-pnpm:oidc-context");
const oidcContext = async (registry, pkg, context) => {
const normalizedRegistry = normalizeUrl(registry);
const normalizedOfficialRegistry = normalizeUrl(OFFICIAL_REGISTRY);
if (normalizedRegistry !== normalizedOfficialRegistry) {
debug$4(
`Registry "${registry}" (normalized: "${normalizedRegistry}") does not match official registry "${OFFICIAL_REGISTRY}" (normalized: "${normalizedOfficialRegistry}"), skipping OIDC check`
);
return false;
}
debug$4(`Registry matches official npm registry, attempting OIDC token exchange for package "${pkg.name ?? "unknown"}"`);
try {
const token = await tokenExchange({ name: pkg.name ?? "" }, context);
if (!token) {
debug$4("OIDC token exchange did not succeed, falling back to NPM_TOKEN authentication");
}
return !!token;
} catch (error) {
debug$4(`OIDC context check failed: ${error instanceof Error ? error.message : String(error)}`);
debug$4("Falling back to NPM_TOKEN authentication");
return false;
}
};
const nerfDart = (url) => {
const parsed = new URL(url);
const from = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
const real = new URL(".", from);
return `//${real.host}${real.pathname}`;
};
const debug$3 = dbg("semantic-release-pnpm:auth");
const setNpmrcAuth = async (npmrc, registry, { cwd, env: { NPM_CONFIG_USERCONFIG, NPM_EMAIL, NPM_PASSWORD, NPM_TOKEN, NPM_USERNAME }, logger }) => {
logger.log("Verify authentication for registry %s", registry);
const { config, files } = rc("npm", {
config: NPM_CONFIG_USERCONFIG ?? resolve(cwd, ".npmrc"),
cwd,
defaults: { registry: OFFICIAL_REGISTRY }
});
if (Array.isArray(files)) {
logger.log("Reading npm config from %s", files.join(", "));
}
const existingToken = getAuthToken(registry, { npmrc: config });
if (existingToken) {
debug$3(`Using existing authentication token from npmrc files for registry "${registry}"`);
await writeFile(npmrc, stringify(config));
return;
}
if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) {
debug$3(`Using username/password/email authentication strategy for registry "${registry}"`);
await writeFile(
npmrc,
`${Object.keys(config).length > 0 ? `${stringify(config)}
` : ""}${nerfDart(registry)}:_auth = \${LEGACY_TOKEN}
email = \${NPM_EMAIL}`
);
logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD, and NPM_EMAIL to ${npmrc}`);
} else if (NPM_TOKEN) {
debug$3(`Using NPM_TOKEN authentication strategy for registry "${registry}"`);
await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)}
` : ""}${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`);
logger.log(`Wrote NPM_TOKEN to ${npmrc}`);
} else {
debug$3(`No authentication credentials found for registry "${registry}"`);
const semanticError = getError("ENONPMTOKEN", { registry });
throw new AggregateError([semanticError], semanticError.message);
}
};
const debug$2 = dbg("semantic-release-pnpm:verify-auth");
const whoamiCache = /* @__PURE__ */ new Map();
const getCacheKey = (registry, context) => {
const normalizedRegistry = normalizeUrl(registry);
const {
env: { NPM_PASSWORD, NPM_TOKEN, NPM_USERNAME }
} = context;
if (NPM_TOKEN) {
const tokenId = NPM_TOKEN.length > 12 ? `${NPM_TOKEN.slice(0, 8)}...${NPM_TOKEN.slice(-4)}` : NPM_TOKEN.slice(0, 8);
return `${normalizedRegistry}:token:${tokenId}`;
}
if (NPM_USERNAME && NPM_PASSWORD) {
return `${normalizedRegistry}:user:${NPM_USERNAME}`;
}
try {
const { config } = rc("npm", {
config: context.env.NPM_CONFIG_USERCONFIG ?? resolve(context.cwd, ".npmrc"),
cwd: context.cwd,
defaults: { registry: OFFICIAL_REGISTRY }
});
const token = getAuthToken(registry, { npmrc: config });
if (token) {
const tokenId = token.length > 12 ? `${token.slice(0, 8)}...${token.slice(-4)}` : token.slice(0, 8);
return `${normalizedRegistry}:token:${tokenId}`;
}
} catch {
}
return `${normalizedRegistry}:default`;
};
const isConnectionError = (error) => {
const errorMessage = error instanceof Error ? error.message : String(error);
const errorCode = error?.code || "";
const isTimedOut = error?.timedOut === true;
return isTimedOut || errorCode === "ECONNREFUSED" || errorCode === "ETIMEDOUT" || errorMessage.includes("ECONNREFUSED") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("getaddrinfo ENOTFOUND") || errorMessage.includes("timed out");
};
const verifyAuthContextAgainstRegistry = async (npmrc, registry, context) => {
const cacheKey = getCacheKey(registry, context);
if (whoamiCache.has(cacheKey)) {
debug$2(`Using cached whoami result for registry "${registry}"`);
const cachedResult = whoamiCache.get(cacheKey);
try {
await cachedResult;
return;
} catch {
debug$2(`Cached whoami result failed, retrying for registry "${registry}"`);
whoamiCache.delete(cacheKey);
}
}
const verificationPromise = (async () => {
const { cwd, env, logger, stderr, stdout } = context;
try {
logger.log(`Running "pnpm whoami" to verify authentication on registry "${registry}"`);
const whoamiResult = await execa("pnpm", ["whoami", "--registry", registry], {
cwd,
env: {
...env,
NPM_CONFIG_USERCONFIG: npmrc
},
preferLocal: true,
timeout: 5e3
// 5 second timeout to prevent hanging when registry is unavailable
});
if (whoamiResult.stdout) {
stdout.write(whoamiResult.stdout);
}
if (whoamiResult.stderr) {
stderr.write(whoamiResult.stderr);
}
} catch (error) {
if (isConnectionError(error)) {
const semanticError2 = getError("EINVALIDNPMAUTH", { registry });
throw new AggregateError([semanticError2], semanticError2.message);
}
const semanticError = getError("EINVALIDNPMTOKEN", { registry });
throw new AggregateError([semanticError], semanticError.message);
}
})();
whoamiCache.set(cacheKey, verificationPromise);
try {
await verificationPromise;
} catch (error) {
if (isConnectionError(error)) {
whoamiCache.delete(cacheKey);
}
throw error;
}
};
const isAuthErrorMessage = (message) => message.includes("requires you to be logged in") || message.includes("authentication") || message.includes("Unauthorized") || message.includes("401") || message.includes("403");
const handlePublishError = (error, registry) => {
if (error instanceof AggregateError) {
throw error;
}
const errorStderrRaw = error?.stderr;
const errorStderr = Array.isArray(errorStderrRaw) ? errorStderrRaw.join("\n") : errorStderrRaw || "";
const errorMessage = error instanceof Error ? error.message : String(error);
const combinedMessage = `${errorStderr} ${errorMessage}`;
if (isConnectionError(error)) {
const semanticError = getError("EINVALIDNPMAUTH", { registry });
throw new AggregateError([semanticError], semanticError.message);
}
if (isAuthErrorMessage(combinedMessage)) {
const semanticError = getError("EINVALIDNPMAUTH", { registry });
throw new AggregateError([semanticError], semanticError.message);
}
throw error;
};
const verifyAuthContextAgainstCustomRegistry = async (npmrc, registry, context, pkgRoot = ".") => {
const { cwd, env, logger, stderr, stdout } = context;
try {
logger.log(`Running "pnpm publish --dry-run" to verify authentication on registry "${registry}"`);
const publishArgs = ["publish", pkgRoot, "--dry-run", "--tag=semantic-release-auth-check", "--registry", registry, "--no-git-checks"];
const publishOptions = {
cwd,
env: {
...env,
NPM_CONFIG_USERCONFIG: npmrc
},
preferLocal: true,
timeout: 5e3
// 5 second timeout to prevent hanging when registry is unavailable
};
const publishResult = await execa("pnpm", publishArgs, publishOptions);
const stdoutString = Array.isArray(publishResult.stdout) ? publishResult.stdout.join("\n") : publishResult.stdout || "";
const stderrString = Array.isArray(publishResult.stderr) ? publishResult.stderr.join("\n") : publishResult.stderr || "";
if (stdoutString) {
stdout.write(stdoutString);
}
if (stderrString) {
stderr.write(stderrString);
if (isAuthErrorMessage(stderrString)) {
const semanticError = getError("EINVALIDNPMAUTH", { registry });
throw new AggregateError([semanticError], semanticError.message);
}
}
} catch (error) {
handlePublishError(error, registry);
}
};
const verifyAuth = async (npmrc, package_, context, pkgRoot) => {
const registry = getRegistry(package_, context);
if (package_.name) {
debug$2(`Checking OIDC trusted publishing for package "${package_.name}" on registry "${registry}"`);
if (await oidcContext(registry, package_, context)) {
debug$2("OIDC trusted publishing verified successfully, exchanging token for npmrc");
const oidcToken = await tokenExchange({ name: package_.name }, context);
if (oidcToken) {
const { config } = rc("npm", {
config: context.env.NPM_CONFIG_USERCONFIG ?? resolve(context.cwd, ".npmrc"),
cwd: context.cwd,
defaults: { registry: OFFICIAL_REGISTRY }
});
await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)}
` : ""}${nerfDart(registry)}:_authToken = ${oidcToken}`);
debug$2(`Wrote OIDC-exchanged token to ${npmrc} for use during publish`);
context.logger.log(`OIDC trusted publishing configured for package "${package_.name}"`);
return;
}
debug$2("OIDC token exchange failed, falling back to NPM_TOKEN authentication");
} else {
debug$2("OIDC trusted publishing not available, falling back to NPM_TOKEN authentication");
}
} else {
debug$2("Package name not found, skipping OIDC check and using NPM_TOKEN authentication");
}
await setNpmrcAuth(npmrc, registry, context);
const normalizedRegistry = normalizeUrl(registry);
const normalizedOfficialRegistry = normalizeUrl(OFFICIAL_REGISTRY);
await (normalizedRegistry === normalizedOfficialRegistry ? verifyAuthContextAgainstRegistry(npmrc, registry, context) : verifyAuthContextAgainstCustomRegistry(npmrc, registry, context, pkgRoot));
};
const isString = (value) => typeof value === "string";
const isNil = (value) => value === null || value === void 0;
const isNonEmptyString = (value) => {
if (!isString(value)) {
return false;
}
return value.trim() !== "";
};
const VALIDATORS = {
branches: Array.isArray,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
npmPublish: (value) => typeof value === "boolean",
pkgRoot: isNonEmptyString,
publishBranch: isNonEmptyString,
tarballDir: isNonEmptyString
};
const verifyConfig = (config) => (
// eslint-disable-next-line unicorn/no-array-reduce
Object.entries(config).reduce((errors, [option, value]) => {
if (isNil(value)) {
return errors;
}
if (!(option in VALIDATORS)) {
return errors;
}
if (VALIDATORS[option]?.(value)) {
return errors;
}
return [...errors, getError(`EINVALID${option.toUpperCase()}`, { [option]: value })];
}, [])
);
const debug$1 = dbg("semantic-release-pnpm:verify-pnpm");
const MIN_PNPM_VERSION = "8.0.0";
const verifyPnpm = async ({ logger }) => {
logger.log(`Verify pnpm version is >= ${MIN_PNPM_VERSION}`);
const version = getPackageManagerVersion("pnpm");
debug$1(`Detected pnpm version: ${String(version)}`);
if (gte(MIN_PNPM_VERSION, version)) {
debug$1(`pnpm version ${String(version)} is below minimum required version ${MIN_PNPM_VERSION}`);
const semanticError = getError("EINVALIDPNPM", { version: String(version) });
throw new AggregateError([semanticError], semanticError.message);
}
debug$1(`pnpm version ${String(version)} meets minimum requirement (>= ${MIN_PNPM_VERSION})`);
};
const verify = async (pluginConfig, context) => {
let errors = verifyConfig(pluginConfig);
let errorsMessage = "";
try {
await verifyPnpm(context);
} catch (error) {
const typedError = error;
errorsMessage += typedError.message;
errors = [...errors, ...typedError.errors ?? [error]];
}
try {
const packageJson = await getPackage(pluginConfig, context);
if (shouldPublish(pluginConfig, packageJson)) {
context.logger.log(`Verifying authentication for package "${packageJson.name ?? "unknown"}"`);
const npmrc = getNpmrcPath(context.cwd, context.env);
await verifyAuth(npmrc, packageJson, context, pluginConfig.pkgRoot);
} else {
context.logger.log(`Skipping authentication verification for package "${packageJson.name ?? "unknown"}" (publishing disabled)`);
}
} catch (error) {
const typedError = error;
errorsMessage += typedError.message;
errors = [...errors, ...typedError.errors ?? [error]];
}
if (errors.length > 0) {
throw new AggregateError(errors, errorsMessage);
}
};
const debug = dbg("semantic-release-pnpm:index");
const PLUGIN_NAME = "semantic-release-pnpm";
let verified;
let prepared;
const verifyConditions = async (pluginConfig, context) => {
if (context.options.publish) {
const publish2 = Array.isArray(context.options.publish) ? context.options.publish : [context.options.publish];
const publishPlugin = publish2.find((config) => config.path && config.path === PLUGIN_NAME) || {};
pluginConfig.npmPublish = pluginConfig.npmPublish ?? publishPlugin.npmPublish;
pluginConfig.tarballDir = pluginConfig.tarballDir ?? publishPlugin.tarballDir;
pluginConfig.pkgRoot = pluginConfig.pkgRoot ?? publishPlugin.pkgRoot;
pluginConfig.disableScripts = pluginConfig.disableScripts ?? publishPlugin.disableScripts;
pluginConfig.branches = pluginConfig.branches ?? publishPlugin.branches;
}
await verify(pluginConfig, context);
verified = true;
};
const prepare = async (pluginConfig, context) => {
if (verified) {
debug("Skipping verifyConditions (already verified)");
} else {
debug("Verification not cached, running verifyConditions");
await verify(pluginConfig, context);
}
await prepare$1(pluginConfig, context);
prepared = true;
};
const publish = async (pluginConfig, context) => {
const packageJson = await getPackage(pluginConfig, context);
if (verified) {
debug("Skipping verifyConditions (already verified)");
} else {
debug("Verification not cached, running verifyConditions");
await verify(pluginConfig, context);
}
if (prepared) {
debug("Skipping prepare (already prepared)");
} else {
debug("Preparation not cached, running prepare");
await prepare$1(pluginConfig, context);
}
return await publish$1(pluginConfig, packageJson, context);
};
const addChannel = async (pluginConfig, context) => {
if (verified) {
debug("Skipping verifyConditions (already verified)");
} else {
debug("Verification not cached, running verifyConditions");
await verify(pluginConfig, context);
}
const packageJson = await getPackage(pluginConfig, context);
return await addChannel$1(pluginConfig, packageJson, context);
};
export { addChannel, prepare, publish, verifyConditions };