UNPKG

@anolilab/semantic-release-pnpm

Version:
493 lines (470 loc) 22.3 kB
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 { URL } from 'node:url'; var __defProp$h = Object.defineProperty; var __name$h = (target, value) => __defProp$h(target, "name", { value, configurable: true }); const getChannel = /* @__PURE__ */ __name$h((channel) => channel ? validRange(channel) ? `release-${channel}` : channel : "latest", "default"); var __defProp$g = Object.defineProperty; var __name$g = (target, value) => __defProp$g(target, "name", { value, configurable: true }); const getNpmrcPath = /* @__PURE__ */ __name$g((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; }, "getNpmrcPath"); const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/"; var __defProp$f = Object.defineProperty; var __name$f = (target, value) => __defProp$f(target, "name", { value, configurable: true }); const getRegistryUrl = /* @__PURE__ */ __name$f((scope, npmrc) => { let url = DEFAULT_NPM_REGISTRY; if (npmrc) { const registryUrl = npmrc[`${scope}:registry`] ?? npmrc.registry; if (registryUrl) { url = registryUrl; } } return url.endsWith("/") ? url : `${url}/`; }, "getRegistryUrl"); const getRegistry = /* @__PURE__ */ __name$f(({ name, publishConfig: { registry } = {} }, { cwd, env }) => registry ?? env.NPM_CONFIG_REGISTRY ?? getRegistryUrl( name.split("/")[0], rc("npm", { config: env.NPM_CONFIG_USERCONFIG ?? resolve(cwd, ".npmrc"), cwd, defaults: { registry: "https://registry.npmjs.org/" } }).config ), "default"); var __defProp$e = Object.defineProperty; var __name$e = (target, value) => __defProp$e(target, "name", { value, configurable: true }); const getReleaseInfo = /* @__PURE__ */ __name$e(({ name }, { env: { DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/" }, 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 }; }, "getReleaseInfo"); var __defProp$d = Object.defineProperty; var __name$d = (target, value) => __defProp$d(target, "name", { value, configurable: true }); const reasonToNotPublish = /* @__PURE__ */ __name$d((pluginConfig, package_) => pluginConfig.npmPublish === false ? "npmPublish plugin option is false" : package_.private === true && package_.workspaces === void 0 ? "package is private and has no workspaces" : null, "reasonToNotPublish"); const shouldPublish = /* @__PURE__ */ __name$d((pluginConfig, package_) => reasonToNotPublish(pluginConfig, package_) === null, "shouldPublish"); var __defProp$c = Object.defineProperty; var __name$c = (target, value) => __defProp$c(target, "name", { value, configurable: true }); const addChannelNpm = /* @__PURE__ */ __name$c(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 result = execa("pnpm", ["dist-tag", "add", `${packageJson.name}@${version}`, distributionTag, "--userconfig", npmrc, "--registry", registry], { cwd, env, 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; }, "default"); var __defProp$b = Object.defineProperty; var __name$b = (target, value) => __defProp$b(target, "name", { value, configurable: true }); const prepareNpm = /* @__PURE__ */ __name$b(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); if (tarballSource !== tarballDestination) { await move(tarballSource, tarballDestination); } } }, "default"); var __defProp$a = Object.defineProperty; var __name$a = (target, value) => __defProp$a(target, "name", { value, configurable: true }); const publishNpm = /* @__PURE__ */ __name$a(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"); } 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; }, "default"); const homepage = "https://github.com/anolilab/semantic-release/tree/main/packages/semantic-release-pnpm"; const packageJson = { homepage: homepage}; var __defProp$9 = Object.defineProperty; var __name$9 = (target, value) => __defProp$9(target, "name", { value, configurable: true }); const linkify = /* @__PURE__ */ __name$9((file) => packageJson.homepage + "/blob/main/" + file, "linkify"); const errors = { EINVALIDBRANCHES: /* @__PURE__ */ __name$9((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." }; }, "EINVALIDBRANCHES"), EINVALIDNPMPUBLISH: /* @__PURE__ */ __name$9(({ 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." }; }, "EINVALIDNPMPUBLISH"), EINVALIDNPMTOKEN: /* @__PURE__ */ __name$9(({ 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)}\`. If you are using Two Factor Authentication for your account, set its level to ["Authorization only"](https://docs.npmjs.com/getting-started/using-two-factor-authentication#levels-of-authentication) in your account settings. **semantic-release** cannot publish with the default "Authorization and writes" level. Please make sure to set the \`NPM_TOKEN\` environment variable in your CI with the exact value of the npm token.`, message: "Invalid npm token." }; }, "EINVALIDNPMTOKEN"), EINVALIDPKGROOT: /* @__PURE__ */ __name$9(({ 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." }; }, "EINVALIDPKGROOT"), EINVALIDPNPM: /* @__PURE__ */ __name$9(({ 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." }; }, "EINVALIDPNPM"), EINVALIDPUBLISHBRANCH: /* @__PURE__ */ __name$9(({ 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." }; }, "EINVALIDPUBLISHBRANCH"), EINVALIDTARBALLDIR: /* @__PURE__ */ __name$9(({ 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." }; }, "EINVALIDTARBALLDIR"), ENONPMTOKEN: /* @__PURE__ */ __name$9(({ registry }) => { return { details: `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 to set it in the \`NPM_TOKEN\` environment variable on your CI environment. The token must allow to publish to the registry \`${String(registry)}\`.`, message: "No npm token specified." }; }, "ENONPMTOKEN"), ENOPKG: /* @__PURE__ */ __name$9(() => { 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." }; }, "ENOPKG"), ENOPKGNAME: /* @__PURE__ */ __name$9(() => { 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`." }; }, "ENOPKGNAME"), ENOPNPM: /* @__PURE__ */ __name$9(() => { return { details: `The Pnpm CLI could not be found in your PATH. Make sure Pnpm is installed and try again.`, message: "Pnpm not found." }; }, "ENOPNPM"), ENOPNPMRC: /* @__PURE__ */ __name$9(() => { return { details: `Didnt find a \`.npmrc\` file or it was not possible to create , in the root of your project.`, message: "Missing `.npmrc` file." }; }, "ENOPNPMRC") }; var __defProp$8 = Object.defineProperty; var __name$8 = (target, value) => __defProp$8(target, "name", { value, configurable: true }); const getError = /* @__PURE__ */ __name$8((code, context = {}) => { const { details, message } = errors[code](context); return new SemanticReleaseError(message, code, details); }, "default"); var __defProp$7 = Object.defineProperty; var __name$7 = (target, value) => __defProp$7(target, "name", { value, configurable: true }); const getPackage = /* @__PURE__ */ __name$7(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; } }, "default"); var __defProp$6 = Object.defineProperty; var __name$6 = (target, value) => __defProp$6(target, "name", { value, configurable: true }); const nerfDart = /* @__PURE__ */ __name$6((url) => { const parsed = new URL(url); const from = `${parsed.protocol}//${parsed.host}${parsed.pathname}`; const real = new URL(".", from); return `//${real.host}${real.pathname}`; }, "nerfDart"); var __defProp$5 = Object.defineProperty; var __name$5 = (target, value) => __defProp$5(target, "name", { value, configurable: true }); const setNpmrcAuth = /* @__PURE__ */ __name$5(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: DEFAULT_NPM_REGISTRY } }); if (Array.isArray(files)) { logger.log("Reading npm config from %s", files.join(", ")); } if (getAuthToken(registry, { npmrc: config })) { await writeFile(npmrc, stringify(config)); return; } if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) { await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)} ` : ""}_auth = \${LEGACY_TOKEN} email = \${NPM_EMAIL}`); logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD, and NPM_EMAIL to ${npmrc}`); } else if (NPM_TOKEN) { await writeFile(npmrc, `${Object.keys(config).length > 0 ? `${stringify(config)} ` : ""}${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`); logger.log(`Wrote NPM_TOKEN to ${npmrc}`); } else { const semanticError = getError("ENONPMTOKEN", { registry }); throw new AggregateError([semanticError], semanticError.message); } }, "default"); var __defProp$4 = Object.defineProperty; var __name$4 = (target, value) => __defProp$4(target, "name", { value, configurable: true }); const verifyAuth = /* @__PURE__ */ __name$4(async (npmrc, package_, context) => { const { cwd, env: { DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/", ...environment }, logger, stderr, stdout } = context; const registry = getRegistry(package_, context); await setNpmrcAuth(npmrc, registry, context); if (normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY)) { try { logger.log(`Running "pnpm whoami" to verify authentication on registry "${registry}"`); const whoamiResult = execa("pnpm", ["whoami", "--userconfig", npmrc, "--registry", registry], { cwd, env: environment, preferLocal: true }); whoamiResult.stdout.pipe(stdout, { end: false }); whoamiResult.stderr.pipe(stderr, { end: false }); await whoamiResult; } catch { const semanticError = getError("EINVALIDNPMTOKEN", { registry }); throw new AggregateError([semanticError], semanticError.message); } } else { logger.log(`Skipping authentication verification for non-default registry "${registry}"`); } }, "default"); var __defProp$3 = Object.defineProperty; var __name$3 = (target, value) => __defProp$3(target, "name", { value, configurable: true }); const isString = /* @__PURE__ */ __name$3((value) => typeof value === "string", "isString"); const isNil = /* @__PURE__ */ __name$3((value) => value === null || value === void 0, "isNil"); const isNonEmptyString = /* @__PURE__ */ __name$3((value) => { if (!isString(value)) { return false; } return value.trim() !== ""; }, "isNonEmptyString"); const VALIDATORS = { branches: Array.isArray, // eslint-disable-next-line @typescript-eslint/no-explicit-any npmPublish: /* @__PURE__ */ __name$3((value) => typeof value === "boolean", "npmPublish"), pkgRoot: isNonEmptyString, publishBranch: isNonEmptyString, tarballDir: isNonEmptyString }; const verifyConfig = /* @__PURE__ */ __name$3((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 })]; }, []) ), "default"); var __defProp$2 = Object.defineProperty; var __name$2 = (target, value) => __defProp$2(target, "name", { value, configurable: true }); const MIN_PNPM_VERSION = "8.0.0"; async function verifyPnpm({ logger }) { logger.log(`Verify pnpm version is >= ${MIN_PNPM_VERSION}`); const version = getPackageManagerVersion("pnpm"); if (gte(MIN_PNPM_VERSION, version)) { const semanticError = getError("EINVALIDPNPM", { version: String(version) }); throw new AggregateError([semanticError], semanticError.message); } } __name$2(verifyPnpm, "verifyPnpm"); var __defProp$1 = Object.defineProperty; var __name$1 = (target, value) => __defProp$1(target, "name", { value, configurable: true }); const verify = /* @__PURE__ */ __name$1(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)) { const npmrc = getNpmrcPath(context.cwd, context.env); await verifyAuth(npmrc, packageJson, context); } } catch (error) { const typedError = error; errorsMessage += typedError.message; errors = [...errors, ...typedError.errors ?? [error]]; } if (errors.length > 0) { throw new AggregateError(errors, errorsMessage); } }, "verify"); var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); const PLUGIN_NAME = "semantic-release-pnpm"; let verified; let prepared; const verifyConditions = /* @__PURE__ */ __name(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; }, "verifyConditions"); const prepare = /* @__PURE__ */ __name(async (pluginConfig, context) => { if (!verified) { await verify(pluginConfig, context); } await prepareNpm(pluginConfig, context); prepared = true; }, "prepare"); const publish = /* @__PURE__ */ __name(async (pluginConfig, context) => { const packageJson = await getPackage(pluginConfig, context); if (!verified) { await verify(pluginConfig, context); } if (!prepared) { await prepareNpm(pluginConfig, context); } return await publishNpm(pluginConfig, packageJson, context); }, "publish"); const addChannel = /* @__PURE__ */ __name(async (pluginConfig, context) => { if (!verified) { await verify(pluginConfig, context); } const packageJson = await getPackage(pluginConfig, context); return await addChannelNpm(pluginConfig, packageJson, context); }, "addChannel"); export { addChannel, prepare, publish, verifyConditions };