@pnpm/plugin-commands-publishing
Version:
The pack and publish commands of pnpm
316 lines • 14 kB
JavaScript
;
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.handler = handler;
exports.api = api;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const zlib_1 = require("zlib");
const error_1 = require("@pnpm/error");
const config_1 = require("@pnpm/config");
const cli_utils_1 = require("@pnpm/cli-utils");
const exportable_manifest_1 = require("@pnpm/exportable-manifest");
const fs_packlist_1 = require("@pnpm/fs.packlist");
const package_bins_1 = require("@pnpm/package-bins");
const tinyglobby_1 = require("tinyglobby");
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 tar_stream_1 = __importDefault(require("tar-stream"));
const publish_js_1 = require("./publish.js");
const chalk_1 = __importDefault(require("chalk"));
const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
const p_limit_1 = __importDefault(require("p-limit"));
const common_cli_options_help_1 = require("@pnpm/common-cli-options-help");
const sort_packages_1 = require("@pnpm/sort-packages");
const logger_1 = require("@pnpm/logger");
const LICENSE_GLOB = 'LICEN{S,C}E{,.*}'; // cspell:disable-line
function rcOptionsTypes() {
return {
...cliOptionsTypes(),
...(0, pick_1.default)([
'npm-path',
], config_1.types),
};
}
function cliOptionsTypes() {
return {
out: String,
recursive: Boolean,
...(0, pick_1.default)([
'dry-run',
'pack-destination',
'pack-gzip-level',
'json',
'workspace-concurrency',
], config_1.types),
};
}
exports.commandNames = ['pack'];
function help() {
return (0, render_help_1.default)({
description: 'Create a tarball from a package',
usages: ['pnpm pack'],
descriptionLists: [
{
title: 'Options',
list: [
{
description: 'Does everything `pnpm pack` would do except actually writing the tarball to disk.',
name: '--dry-run',
},
{
description: 'Directory in which `pnpm pack` will save tarballs. The default is the current working directory.',
name: '--pack-destination <dir>',
},
{
description: 'Prints the packed tarball and contents in the json format.',
name: '--json',
},
{
description: 'Customizes the output path for the tarball. Use `%s` and `%v` to include the package name and version, e.g., `%s.tgz` or `some-dir/%s-%v.tgz`. By default, the tarball is saved in the current working directory with the name `<package-name>-<version>.tgz`.',
name: '--out <path>',
},
{
description: 'Pack all packages from the workspace',
name: '--recursive',
shortAlias: '-r',
},
{
description: `Set the maximum number of concurrency. Default is ${(0, config_1.getDefaultWorkspaceConcurrency)()}. For unlimited concurrency use Infinity.`,
name: '--workspace-concurrency <number>',
},
],
},
common_cli_options_help_1.FILTERING,
],
});
}
async function handler(opts) {
const packedPackages = [];
if (opts.recursive) {
const selectedProjectsGraph = opts.selectedProjectsGraph;
const pkgsToPack = [];
for (const { package: pkg } of Object.values(selectedProjectsGraph)) {
if (pkg.manifest.name && pkg.manifest.version) {
pkgsToPack.push(pkg);
}
}
const packedPkgDirs = new Set(pkgsToPack.map(({ rootDir }) => rootDir));
if (packedPkgDirs.size === 0) {
logger_1.logger.info({
message: 'There are no packages that should be packed',
prefix: opts.dir,
});
}
const chunks = (0, sort_packages_1.sortPackages)(selectedProjectsGraph);
const limitPack = (0, p_limit_1.default)((0, config_1.getWorkspaceConcurrency)(opts.workspaceConcurrency));
const resolvedOpts = { ...opts };
if (opts.out) {
resolvedOpts.out = path_1.default.resolve(opts.dir, opts.out);
}
else if (opts.packDestination) {
resolvedOpts.packDestination = path_1.default.resolve(opts.dir, opts.packDestination);
}
else {
resolvedOpts.packDestination = path_1.default.resolve(opts.dir);
}
for (const chunk of chunks) {
// eslint-disable-next-line no-await-in-loop
await Promise.all(chunk.map(pkgDir => limitPack(async () => {
if (!packedPkgDirs.has(pkgDir))
return;
const pkg = selectedProjectsGraph[pkgDir].package;
const packResult = await api({
...resolvedOpts,
dir: pkg.rootDir,
});
packedPackages.push(toPackResultJson(packResult));
})));
}
}
else {
const packResult = await api(opts);
packedPackages.push(toPackResultJson(packResult));
}
if (opts.json) {
return JSON.stringify(packedPackages.length > 1 ? packedPackages : packedPackages[0], null, 2);
}
return packedPackages.map(({ name, version, filename, files }) => `${opts.unicode ? '📦 ' : 'package:'} ${name}@${version}
${chalk_1.default.blueBright('Tarball Contents')}
${files.map(({ path }) => path).join('\n')}
${chalk_1.default.blueBright('Tarball Details')}
${filename}`).join('\n\n');
}
async function api(opts) {
const { manifest: entryManifest, fileName: manifestFileName } = await (0, cli_utils_1.readProjectManifest)(opts.dir, opts);
preventBundledDependenciesWithoutHoistedNodeLinker(opts.nodeLinker, entryManifest);
const _runScriptsIfPresent = publish_js_1.runScriptsIfPresent.bind(null, {
depPath: opts.dir,
extraBinPaths: opts.extraBinPaths,
extraEnv: opts.extraEnv,
pkgRoot: opts.dir,
rawConfig: opts.rawConfig,
rootModulesDir: await (0, realpath_missing_1.default)(path_1.default.join(opts.dir, 'node_modules')),
stdio: 'inherit',
unsafePerm: true, // when running scripts explicitly, assume that they're trusted.
});
if (!opts.ignoreScripts) {
await _runScriptsIfPresent([
'prepack',
'prepare',
], entryManifest);
}
const dir = entryManifest.publishConfig?.directory
? path_1.default.join(opts.dir, entryManifest.publishConfig.directory)
: opts.dir;
// always read the latest manifest, as "prepack" or "prepare" script may modify package manifest.
const { manifest } = await (0, cli_utils_1.readProjectManifest)(dir, opts);
preventBundledDependenciesWithoutHoistedNodeLinker(opts.nodeLinker, manifest);
if (!manifest.name) {
throw new error_1.PnpmError('PACKAGE_NAME_NOT_FOUND', `Package name is not defined in the ${manifestFileName}.`);
}
if (!(0, validate_npm_package_name_1.default)(manifest.name).validForOldPackages) {
throw new error_1.PnpmError('INVALID_PACKAGE_NAME', `Invalid package name "${manifest.name}".`);
}
if (!manifest.version) {
throw new error_1.PnpmError('PACKAGE_VERSION_NOT_FOUND', `Package version is not defined in the ${manifestFileName}.`);
}
let tarballName;
let packDestination;
const normalizedName = manifest.name.replace('@', '').replace('/', '-');
if (opts.out) {
if (opts.packDestination) {
throw new error_1.PnpmError('INVALID_OPTION', 'Cannot use --pack-destination and --out together');
}
const preparedOut = opts.out.replaceAll('%s', normalizedName).replaceAll('%v', manifest.version);
const parsedOut = path_1.default.parse(preparedOut);
packDestination = parsedOut.dir ? parsedOut.dir : opts.packDestination;
tarballName = parsedOut.base;
}
else {
tarballName = `${normalizedName}-${manifest.version}.tgz`;
packDestination = opts.packDestination;
}
const publishManifest = await createPublishManifest({
projectDir: dir,
modulesDir: path_1.default.join(opts.dir, 'node_modules'),
manifest,
embedReadme: opts.embedReadme,
catalogs: opts.catalogs ?? {},
hooks: opts.hooks,
});
const files = await (0, fs_packlist_1.packlist)(dir, {
packageJsonCache: {
[path_1.default.join(dir, 'package.json')]: publishManifest,
},
});
const filesMap = Object.fromEntries(files.map((file) => [`package/${file}`, path_1.default.join(dir, file)]));
// cspell:disable-next-line
if (opts.workspaceDir != null && dir !== opts.workspaceDir && !files.some((file) => /LICEN[CS]E(?:\..+)?/i.test(file))) {
const licenses = await (0, tinyglobby_1.glob)([LICENSE_GLOB], { cwd: opts.workspaceDir, expandDirectories: false });
for (const license of licenses) {
filesMap[`package/${license}`] = path_1.default.join(opts.workspaceDir, license);
}
}
const destDir = packDestination
? (path_1.default.isAbsolute(packDestination) ? packDestination : path_1.default.join(dir, packDestination ?? '.'))
: dir;
if (!opts.dryRun) {
await fs_1.default.promises.mkdir(destDir, { recursive: true });
await packPkg({
destFile: path_1.default.join(destDir, tarballName),
filesMap,
modulesDir: path_1.default.join(opts.dir, 'node_modules'),
packGzipLevel: opts.packGzipLevel,
manifest: publishManifest,
bins: [
...(await (0, package_bins_1.getBinsFromPackageManifest)(publishManifest, dir)).map(({ path }) => path),
...(manifest.publishConfig?.executableFiles ?? [])
.map((executableFile) => path_1.default.join(dir, executableFile)),
],
});
if (!opts.ignoreScripts) {
await _runScriptsIfPresent(['postpack'], entryManifest);
}
}
let packedTarballPath;
if (opts.dir !== destDir) {
packedTarballPath = path_1.default.join(destDir, tarballName);
}
else {
packedTarballPath = path_1.default.relative(opts.dir, path_1.default.join(dir, tarballName));
}
const packedContents = files.sort((a, b) => a.localeCompare(b, 'en'));
return {
publishedManifest: publishManifest,
contents: packedContents,
tarballPath: packedTarballPath,
};
}
function preventBundledDependenciesWithoutHoistedNodeLinker(nodeLinker, manifest) {
if (nodeLinker === 'hoisted')
return;
for (const key of ['bundledDependencies', 'bundleDependencies']) {
const bundledDependencies = manifest[key];
if (bundledDependencies) {
throw new error_1.PnpmError('BUNDLED_DEPENDENCIES_WITHOUT_HOISTED', `${key} does not work with "nodeLinker: ${nodeLinker}"`, {
hint: `Add "nodeLinker: hoisted" to pnpm-workspace.yaml or delete ${key} from the root package.json to resolve this error`,
});
}
}
}
async function readReadmeFile(projectDir) {
const files = await fs_1.default.promises.readdir(projectDir);
const readmePath = files.find(name => /readme\.md$/i.test(name));
const readmeFile = readmePath ? await fs_1.default.promises.readFile(path_1.default.join(projectDir, readmePath), 'utf8') : undefined;
return readmeFile;
}
async function packPkg(opts) {
const { destFile, filesMap, bins, manifest, } = opts;
const mtime = new Date('1985-10-26T08:15:00.000Z');
const pack = tar_stream_1.default.pack();
await Promise.all(Object.entries(filesMap).map(async ([name, source]) => {
const isExecutable = bins.some((bin) => path_1.default.relative(bin, source) === '');
const mode = isExecutable ? 0o755 : 0o644;
if (/^package\/package\.(?:json|json5|yaml)$/.test(name)) {
pack.entry({ mode, mtime, name: 'package/package.json' }, JSON.stringify(manifest, null, 2));
return;
}
pack.entry({ mode, mtime, name }, fs_1.default.readFileSync(source));
}));
const tarball = fs_1.default.createWriteStream(destFile);
pack.pipe((0, zlib_1.createGzip)({ level: opts.packGzipLevel })).pipe(tarball);
pack.finalize();
return new Promise((resolve, reject) => {
tarball.on('close', () => {
resolve();
}).on('error', reject);
});
}
async function createPublishManifest(opts) {
const { projectDir, embedReadme, modulesDir, manifest, catalogs, hooks } = opts;
const readmeFile = embedReadme ? await readReadmeFile(projectDir) : undefined;
return (0, exportable_manifest_1.createExportableManifest)(projectDir, manifest, {
catalogs,
hooks,
readmeFile,
modulesDir,
});
}
function toPackResultJson(packResult) {
const { publishedManifest, contents, tarballPath } = packResult;
return {
name: publishedManifest.name,
version: publishedManifest.version,
filename: tarballPath,
files: contents.map((file) => ({ path: file })),
};
}
//# sourceMappingURL=pack.js.map