UNPKG

@anolilab/semantic-release-pnpm

Version:
804 lines (775 loc) 33.9 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 } 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 };