@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 };