@anolilab/semantic-release-pnpm
Version: 
Semantic-release plugin to publish a npm package with pnpm.
493 lines (470 loc) • 22.3 kB
JavaScript
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 };