@pnpm/plugin-commands-publishing
Version:
The pack and publish commands of pnpm
326 lines • 13.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.commandNames = void 0;
exports.rcOptionsTypes = rcOptionsTypes;
exports.cliOptionsTypes = cliOptionsTypes;
exports.help = help;
exports.removePnpmSpecificOptions = removePnpmSpecificOptions;
exports.handler = handler;
exports.publish = publish;
exports.runScriptsIfPresent = runScriptsIfPresent;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const cli_utils_1 = require("@pnpm/cli-utils");
const common_cli_options_help_1 = require("@pnpm/common-cli-options-help");
const config_1 = require("@pnpm/config");
const error_1 = require("@pnpm/error");
const lifecycle_1 = require("@pnpm/lifecycle");
const run_npm_1 = require("@pnpm/run-npm");
const git_utils_1 = require("@pnpm/git-utils");
const network_auth_header_1 = require("@pnpm/network.auth-header");
const plugin_commands_env_1 = require("@pnpm/plugin-commands-env");
const enquirer_1 = require("enquirer");
const rimraf_1 = __importDefault(require("@zkochan/rimraf"));
const pick_1 = __importDefault(require("ramda/src/pick"));
const realpath_missing_1 = __importDefault(require("realpath-missing"));
const render_help_1 = __importDefault(require("render-help"));
const tempy_1 = __importDefault(require("tempy"));
const pack = __importStar(require("./pack.js"));
const recursivePublish_js_1 = require("./recursivePublish.js");
function rcOptionsTypes() {
return (0, pick_1.default)([
'access',
'git-checks',
'ignore-scripts',
'provenance',
'npm-path',
'otp',
'publish-branch',
'registry',
'tag',
'unsafe-perm',
'embed-readme',
], config_1.types);
}
function cliOptionsTypes() {
return {
...rcOptionsTypes(),
'dry-run': Boolean,
force: Boolean,
json: Boolean,
recursive: Boolean,
'report-summary': Boolean,
};
}
exports.commandNames = ['publish'];
function help() {
return (0, render_help_1.default)({
description: 'Publishes a package to the npm registry.',
descriptionLists: [
{
title: 'Options',
list: [
{
description: "Don't check if current branch is your publish branch, clean, and up to date",
name: '--no-git-checks',
},
{
description: 'Sets branch name to publish. Default is master',
name: '--publish-branch',
},
{
description: 'Does everything a publish would do except actually publishing to the registry',
name: '--dry-run',
},
{
description: 'Show information in JSON format',
name: '--json',
},
{
description: 'Registers the published package with the given tag. By default, the "latest" tag is used.',
name: '--tag <tag>',
},
{
description: 'Tells the registry whether this package should be published as public or restricted',
name: '--access <public|restricted>',
},
{
description: 'Ignores any publish related lifecycle scripts (prepublishOnly, postpublish, and the like)',
name: '--ignore-scripts',
},
{
description: 'Packages are proceeded to be published even if their current version is already in the registry. This is useful when a "prepublishOnly" script bumps the version of the package before it is published',
name: '--force',
},
{
description: 'Save the list of the newly published packages to "pnpm-publish-summary.json". Useful when some other tooling is used to report the list of published packages.',
name: '--report-summary',
},
{
description: 'When publishing packages that require two-factor authentication, this option can specify a one-time password',
name: '--otp',
},
{
description: 'Publish all packages from the workspace',
name: '--recursive',
shortAlias: '-r',
},
],
},
common_cli_options_help_1.FILTERING,
],
url: (0, cli_utils_1.docsUrl)('publish'),
usages: ['pnpm publish [<tarball>|<dir>] [--tag <tag>] [--access <public|restricted>] [options]'],
});
}
const GIT_CHECKS_HINT = 'If you want to disable Git checks on publish, set the "git-checks" setting to "false", or run again with "--no-git-checks".';
/**
* Remove pnpm-specific CLI options that npm doesn't recognize.
*/
function removePnpmSpecificOptions(args) {
const booleanOptions = new Set([
'--no-git-checks',
'--embed-readme',
'--no-embed-readme',
]);
const optionsWithValue = new Set([
'--publish-branch',
'--npm-path',
]);
const result = [];
let i = 0;
while (i < args.length) {
const arg = args[i];
if (booleanOptions.has(arg)) {
// Skip only the boolean option itself
i++;
}
else if (optionsWithValue.has(arg)) {
// Skip the option and its value
i++;
// Skip the value if it exists and doesn't look like another option
if (i < args.length && args[i][0] !== '-') {
i++;
}
}
else {
result.push(arg);
i++;
}
}
return result;
}
async function handler(opts, params) {
const result = await publish(opts, params);
if (result?.manifest)
return;
return result;
}
async function publish(opts, params) {
if (opts.gitChecks !== false && await (0, git_utils_1.isGitRepo)()) {
if (!(await (0, git_utils_1.isWorkingTreeClean)())) {
throw new error_1.PnpmError('GIT_UNCLEAN', 'Unclean working tree. Commit or stash changes first.', {
hint: GIT_CHECKS_HINT,
});
}
const branches = opts.publishBranch ? [opts.publishBranch] : ['master', 'main'];
const currentBranch = await (0, git_utils_1.getCurrentBranch)();
if (currentBranch === null) {
throw new error_1.PnpmError('GIT_UNKNOWN_BRANCH', `The Git HEAD may not attached to any branch, but your "publish-branch" is set to "${branches.join('|')}".`, {
hint: GIT_CHECKS_HINT,
});
}
if (!branches.includes(currentBranch)) {
const { confirm } = await (0, enquirer_1.prompt)({
message: `You're on branch "${currentBranch}" but your "publish-branch" is set to "${branches.join('|')}". \
Do you want to continue?`,
name: 'confirm',
type: 'confirm',
}); // eslint-disable-line @typescript-eslint/no-explicit-any
if (!confirm) {
throw new error_1.PnpmError('GIT_NOT_CORRECT_BRANCH', `Branch is not on '${branches.join('|')}'.`, {
hint: GIT_CHECKS_HINT,
});
}
}
if (!(await (0, git_utils_1.isRemoteHistoryClean)())) {
throw new error_1.PnpmError('GIT_NOT_LATEST', 'Remote history differs. Please pull changes.', {
hint: GIT_CHECKS_HINT,
});
}
}
if (opts.recursive && (opts.selectedProjectsGraph != null)) {
const { exitCode } = await (0, recursivePublish_js_1.recursivePublish)({
...opts,
selectedProjectsGraph: opts.selectedProjectsGraph,
workspaceDir: opts.workspaceDir ?? process.cwd(),
});
return { exitCode };
}
let args = opts.argv.original.slice(1);
const dirInParams = (params.length > 0) ? params[0] : undefined;
if (dirInParams) {
args = args.filter(arg => arg !== params[0]);
}
args = removePnpmSpecificOptions(args);
if (dirInParams != null && (dirInParams.endsWith('.tgz') || dirInParams?.endsWith('.tar.gz'))) {
const { status } = (0, run_npm_1.runNpm)(opts.npmPath, ['publish', dirInParams, ...args]);
return { exitCode: status ?? 0 };
}
const dir = dirInParams ?? opts.dir ?? process.cwd();
const _runScriptsIfPresent = runScriptsIfPresent.bind(null, {
depPath: dir,
extraBinPaths: opts.extraBinPaths,
extraEnv: opts.extraEnv,
pkgRoot: dir,
rawConfig: opts.rawConfig,
rootModulesDir: await (0, realpath_missing_1.default)(path_1.default.join(dir, 'node_modules')),
stdio: 'inherit',
unsafePerm: true, // when running scripts explicitly, assume that they're trusted.
prepareExecutionEnv: plugin_commands_env_1.prepareExecutionEnv.bind(null, opts),
});
const { manifest } = await (0, cli_utils_1.readProjectManifest)(dir, opts);
// Unfortunately, we cannot support postpack at the moment
if (!opts.ignoreScripts) {
await _runScriptsIfPresent([
'prepublishOnly',
'prepublish',
], manifest);
}
// We have to publish the tarball from another location.
// Otherwise, npm would publish the package with the package.json file
// from the current working directory, ignoring the package.json file
// that was generated and packed to the tarball.
const packDestination = tempy_1.default.directory();
const { tarballPath } = await pack.api({
...opts,
dir,
packDestination,
dryRun: false,
});
await copyNpmrc({ dir, workspaceDir: opts.workspaceDir, packDestination });
const { status } = (0, run_npm_1.runNpm)(opts.npmPath, ['publish', '--ignore-scripts', path_1.default.basename(tarballPath), ...args], {
cwd: packDestination,
env: getEnvWithTokens(opts),
});
await (0, rimraf_1.default)(packDestination);
if (status != null && status !== 0) {
return { exitCode: status };
}
if (!opts.ignoreScripts) {
await _runScriptsIfPresent([
'publish',
'postpublish',
], manifest);
}
return { manifest };
}
/**
* The npm CLI doesn't support token helpers, so we transform the token helper settings
* to regular auth token settings that the npm CLI can understand.
*/
function getEnvWithTokens(opts) {
const tokenHelpers = Object.entries(opts.rawConfig).filter(([key]) => key.endsWith(':tokenHelper'));
const tokenHelpersFromArgs = opts.argv.original
.filter(arg => arg.includes(':tokenHelper='))
.map(arg => arg.split('=', 2));
const env = {};
for (const [key, helperPath] of tokenHelpers.concat(tokenHelpersFromArgs)) {
const authHeader = (0, network_auth_header_1.loadToken)(helperPath, key);
const authType = authHeader.startsWith('Bearer')
? '_authToken'
: '_auth';
const registry = key.replace(/:tokenHelper$/, '');
env[`NPM_CONFIG_${registry}:${authType}`] = authType === '_authToken'
? authHeader.slice('Bearer '.length)
: authHeader.replace(/Basic /i, '');
}
return env;
}
async function copyNpmrc({ dir, workspaceDir, packDestination }) {
const localNpmrc = path_1.default.join(dir, '.npmrc');
if ((0, fs_1.existsSync)(localNpmrc)) {
await fs_1.promises.copyFile(localNpmrc, path_1.default.join(packDestination, '.npmrc'));
return;
}
if (!workspaceDir)
return;
const workspaceNpmrc = path_1.default.join(workspaceDir, '.npmrc');
if ((0, fs_1.existsSync)(workspaceNpmrc)) {
await fs_1.promises.copyFile(workspaceNpmrc, path_1.default.join(packDestination, '.npmrc'));
}
}
async function runScriptsIfPresent(opts, scriptNames, manifest) {
for (const scriptName of scriptNames) {
if (!manifest.scripts?.[scriptName])
continue;
await (0, lifecycle_1.runLifecycleHook)(scriptName, manifest, opts); // eslint-disable-line no-await-in-loop
}
}
//# sourceMappingURL=publish.js.map