UNPKG

@anolilab/semantic-release-pnpm

Version:
776 lines (747 loc) 33.1 kB
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, ExecaError } from 'execa'; import { validRange, major, gte } from 'semver'; import { isAccessibleSync, ensureFileSync, isAccessible, readJson, writeJson, move, writeFile } from '@visulima/fs'; import { resolve } from '@visulima/path'; import { rc } from '@anolilab/rc'; import normalizeUrl from 'normalize-url'; import { getPackageManagerVersion, findPackageJson } 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 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 pnpmVersion = getPackageManagerVersion("pnpm"); const pnpmMajor = major(pnpmVersion); debug$7("Detected pnpm major version: %d", pnpmMajor); const versionBin = pnpmMajor >= 10 ? "npm" : "pnpm"; const versionArguments = pnpmMajor >= 10 ? ["pkg", "set", `version=${version}`] : ["version", version, "--no-git-tag-version", "--allow-same-version"]; const versionResult = execa(versionBin, versionArguments, { cwd: basePath, env, preferLocal: true }); versionResult.stdout.pipe(stdout, { end: false }); versionResult.stderr.pipe(stderr, { end: false }); await versionResult; if (pnpmMajor >= 10) { const shrinkwrapPath = resolve(basePath, "npm-shrinkwrap.json"); if (await isAccessible(shrinkwrapPath)) { debug$7(`Updating npm-shrinkwrap.json at ${shrinkwrapPath}`); const shrinkwrap = await readJson(shrinkwrapPath); shrinkwrap.version = version; await writeJson(shrinkwrapPath, shrinkwrap, { indent: 2 }); } } 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 ALREADY_PUBLISHED_PHRASE = "cannot publish over the previously published versions"; const isAlreadyPublishedError = (error) => { const haystacks = [error.stderr, error.shortMessage, error.message].filter((value) => typeof value === "string"); return haystacks.some((value) => value.includes(ALREADY_PUBLISHED_PHRASE)); }; const handlePublishError = (error, packageJson, context, distributionTag, registry) => { const { logger, nextRelease: { version } } = context; const errorMessage = error instanceof Error ? error.message : String(error); if (error instanceof ExecaError && isAlreadyPublishedError(error)) { logger.log(`Package ${packageJson.name ?? ""}@${version} is already published at dist-tag @${distributionTag} on ${registry}, skipping`); return getReleaseInfo(packageJson, context, distributionTag, registry); } logger.log(`Failed to publish ${packageJson.name ?? ""}@${version} to dist-tag @${distributionTag} on ${registry}: ${errorMessage}`); throw new AggregateError([error], errorMessage, { cause: error }); }; 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) { return handlePublishError(error, packageJson, context, distributionTag, registry); } 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\` or \`false\`. 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, { cause: error }); } 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: ${String(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 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 idToken = process.env.NPM_ID_TOKEN; if (idToken) { debug$5("NPM_ID_TOKEN environment variable detected, using it for OIDC token exchange"); return exchangeIdToken(idToken, pkg.name, context); } const ciEnv = envCi(); const ciProviderName = typeof ciEnv.name === "string" ? ciEnv.name : void 0; if (!ciProviderName) { debug$5("Unable to detect CI provider for OIDC token exchange"); return Promise.resolve(void 0); } debug$5(`Detected CI provider: ${ciProviderName}`); if (GITHUB_ACTIONS_PROVIDER_NAME === ciProviderName) { return exchangeGithubActionsToken(pkg.name, context); } debug$5(`CI provider "${ciProviderName}" does not have built-in OIDC support; set NPM_ID_TOKEN to use trusted publishing`); 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 tokenValue = token.token; const tokenId = tokenValue.length > 12 ? `${tokenValue.slice(0, 8)}...${tokenValue.slice(-4)}` : tokenValue.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 semanticError = getError("EINVALIDNPMAUTH", { registry }); throw new AggregateError([semanticError], semanticError.message, { cause: error }); } if (normalizeUrl(registry) === normalizeUrl(OFFICIAL_REGISTRY)) { const semanticError = getError("EINVALIDNPMTOKEN", { registry }); throw new AggregateError([semanticError], semanticError.message, { cause: error }); } const errorMessage = error instanceof Error ? error.message : String(error); context.logger.warn( `Could not verify auth via "pnpm whoami" on custom registry "${registry}" (${errorMessage}). This registry may not support /-/whoami for the configured token type (e.g. GitLab Package Registry with deploy tokens). The publish step will surface any real credential errors.` ); } })(); whoamiCache.set(cacheKey, verificationPromise); try { await verificationPromise; } catch (error) { if (isConnectionError(error)) { whoamiCache.delete(cacheKey); } throw error; } }; const verifyAuth = async (npmrc, package_, context) => { 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); await verifyAuthContextAgainstRegistry(npmrc, registry, context); }; 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, // eslint-disable-next-line @typescript-eslint/no-explicit-any tarballDir: (value) => value === false || isNonEmptyString(value) }; 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 = ({ logger }) => { logger.log(`Verify pnpm version is >= ${MIN_PNPM_VERSION}`); const version = getPackageManagerVersion("pnpm"); debug$1(`Detected pnpm version: ${version}`); if (gte(MIN_PNPM_VERSION, version)) { debug$1(`pnpm version ${version} is below minimum required version ${MIN_PNPM_VERSION}`); const semanticError = getError("EINVALIDPNPM", { version }); throw new AggregateError([semanticError], semanticError.message); } debug$1(`pnpm version ${version} meets minimum requirement (>= ${MIN_PNPM_VERSION})`); }; const verify = async (pluginConfig, context) => { let errors = verifyConfig(pluginConfig); let errorsMessage = ""; try { verifyPnpm(context); } catch (error) { const typedError = error; errorsMessage += typedError.message; errors = [...errors, ...Array.isArray(typedError.errors) ? typedError.errors : [typedError]]; } 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); } else { context.logger.log(`Skipping authentication verification for package "${packageJson.name ?? "unknown"}" (publishing disabled)`); } } catch (error) { const typedError = error; errorsMessage += typedError.message; errors = [...errors, ...Array.isArray(typedError.errors) ? typedError.errors : [typedError]]; } 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 };