@lerna/publish
Version:
Publish packages in the current project
423 lines (422 loc) • 17.9 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var import_core = require("@lerna/core");
var import_dedent = __toESM(require("dedent"));
var import_get_port = __toESM(require("get-port"));
var import_npm_package_arg = __toESM(require("npm-package-arg"));
var import_p_map = __toESM(require("p-map"));
var import_p_map_series = __toESM(require("p-map-series"));
var import_p_waterfall = __toESM(require("p-waterfall"));
var import_path = __toESM(require("path"));
const { hasDependencyInstalled } = require("./lib/has-dependency-installed");
const { isHoistedPackage } = require("./lib/is-hoisted-package");
module.exports = function factory(argv) {
return new BootstrapCommand(argv);
};
class BootstrapCommand extends import_core.Command {
get requiresGit() {
return false;
}
initialize() {
const { registry, npmClient = "npm", npmClientArgs = [], mutex, hoist, nohoist } = this.options;
if (npmClient === "pnpm") {
throw new import_core.ValidationError(
"EWORKSPACES",
"Bootstrapping with pnpm is not supported. Use pnpm directly to manage dependencies: https://pnpm.io/cli/install"
);
}
if (npmClient === "yarn" && hoist) {
throw new import_core.ValidationError(
"EWORKSPACES",
import_dedent.default`
--hoist is not supported with --npm-client=yarn, use yarn workspaces instead
A guide is available at https://yarnpkg.com/blog/2017/08/02/introducing-workspaces/
`
);
}
if (npmClient === "yarn" && this.project.manifest.get("workspaces") && this.options.useWorkspaces !== true) {
throw new import_core.ValidationError(
"EWORKSPACES",
import_dedent.default`
Yarn workspaces are configured in package.json, but not enabled in lerna.json!
Please choose one: useWorkspaces = true in lerna.json, or remove package.json workspaces config
`
);
}
const { LERNA_EXEC_PATH = "leaf", LERNA_ROOT_PATH = "root" } = process.env;
if (LERNA_EXEC_PATH === LERNA_ROOT_PATH) {
this.logger.warn("bootstrap", "Skipping recursive execution");
return false;
}
if (hoist) {
let hoisting;
if (hoist === true) {
hoisting = ["**"];
} else {
hoisting = [].concat(hoist);
}
if (nohoist) {
if (!Array.isArray(nohoist)) {
hoisting = hoisting.concat(`!${nohoist}`);
} else {
hoisting = hoisting.concat(nohoist.map((str) => `!${str}`));
}
}
this.logger.verbose("hoist", "using globs %j", hoisting);
this.hoisting = hoisting;
}
this.runPackageLifecycle = (0, import_core.createRunner)({ registry });
this.npmConfig = {
registry,
npmClient,
npmClientArgs,
mutex
};
if (npmClient === "npm" && this.options.ci && (0, import_core.hasNpmVersion)(">=5.7.0")) {
this.npmConfig.subCommand = this.hoisting ? "install" : "ci";
if (this.hoisting) {
this.npmConfig.npmClientArgs.unshift("--no-save");
}
}
const doubleDashArgs = this.options["--"] || [];
if (doubleDashArgs.length) {
this.npmConfig.npmClientArgs = [...npmClientArgs, ...doubleDashArgs];
}
if (this.options.ignoreScripts) {
this.npmConfig.npmClientArgs.unshift("--ignore-scripts");
}
this.targetGraph = this.options.forceLocal ? new import_core.PackageGraph(this.packageGraph.rawPackageList, "allDependencies", "forceLocal") : this.packageGraph;
let chain = Promise.resolve();
chain = chain.then(() => {
return (0, import_core.getFilteredPackages)(this.targetGraph, this.execOpts, this.options);
});
chain = chain.then((filteredPackages) => {
this.filteredPackages = filteredPackages;
if (this.options.contents) {
for (const pkg of filteredPackages) {
pkg.contents = this.options.contents;
}
}
if (filteredPackages.length !== this.targetGraph.size && !this.options.forceLocal) {
this.logger.warn("bootstrap", "Installing local packages that do not match filters from registry");
this.targetGraph = new import_core.PackageGraph(filteredPackages, "allDependencies", this.options.forceLocal);
this.npmConfig.npmClientArgs.unshift(npmClient === "yarn" ? "--pure-lockfile" : "--no-save");
if (this.npmConfig.subCommand === "ci") {
this.npmConfig.subCommand = "install";
}
}
});
chain = chain.then(() => {
if (npmClient === "yarn" && !mutex) {
return (0, import_get_port.default)({ port: 42424, host: "0.0.0.0" }).then((port) => {
this.npmConfig.mutex = `network:${port}`;
this.logger.silly("npmConfig", this.npmConfig);
});
}
this.logger.silly("npmConfig", this.npmConfig);
});
return chain;
}
execute() {
if (this.options.useWorkspaces || this.rootHasLocalFileDependencies()) {
if (this.options.rejectCycles) {
this.packageGraph.collapseCycles({ rejectCycles: this.options.rejectCycles });
}
return this.installRootPackageOnly();
}
const filteredLength = this.filteredPackages.length;
const packageCountLabel = `${filteredLength} package${filteredLength > 1 ? "s" : ""}`;
const scriptsEnabled = this.options.ignoreScripts !== true;
this.enableProgressBar();
this.logger.info("", `Bootstrapping ${packageCountLabel}`);
const tasks = [];
if (scriptsEnabled) {
tasks.push(() => this.runLifecycleInPackages("preinstall"));
}
tasks.push(
() => this.getDependenciesToInstall(),
(result) => this.installExternalDependencies(result),
() => this.symlinkPackages()
);
if (scriptsEnabled) {
tasks.push(
() => this.runLifecycleInPackages("install"),
() => this.runLifecycleInPackages("postinstall")
);
if (!this.options.ignorePrepublish) {
tasks.push(() => this.runLifecycleInPackages("prepublish"));
}
tasks.push(() => this.runLifecycleInPackages("prepare"));
}
return (0, import_p_waterfall.default)(tasks).then(() => {
this.logger.success("", `Bootstrapped ${packageCountLabel}`);
});
}
installRootPackageOnly() {
this.logger.info("bootstrap", "root only");
this.npmConfig.stdio = "inherit";
return (0, import_core.npmInstall)(this.project.manifest, this.npmConfig);
}
/**
* If the root manifest has local dependencies with `file:` specifiers,
* all the complicated bootstrap logic should be skipped in favor of
* npm5's package-locked auto-hoisting.
* @returns {Boolean}
*/
rootHasLocalFileDependencies() {
const rootDependencies = Object.assign({}, this.project.manifest.dependencies);
return Object.keys(rootDependencies).some(
(name) => this.targetGraph.has(name) && import_npm_package_arg.default.resolve(name, rootDependencies[name], this.project.rootPath).type === "directory"
);
}
runLifecycleInPackages(stage) {
this.logger.verbose("lifecycle", stage);
if (!this.filteredPackages.length) {
return;
}
const tracker = this.logger.newItem(stage);
const mapPackageWithScript = (pkg) => this.runPackageLifecycle(pkg, stage).then(() => {
tracker.completeWork(1);
});
tracker.addWork(this.filteredPackages.length);
const runner = this.toposort ? (0, import_core.runTopologically)(this.filteredPackages, mapPackageWithScript, {
concurrency: this.concurrency,
rejectCycles: this.options.rejectCycles
}) : (0, import_p_map.default)(this.filteredPackages, mapPackageWithScript, { concurrency: this.concurrency });
return runner.finally(() => tracker.finish());
}
hoistedDirectory(dependency) {
return import_path.default.join(this.project.rootPath, "node_modules", dependency);
}
hoistedPackageJson(dependency) {
try {
return require(import_path.default.join(this.hoistedDirectory(dependency), "package.json"));
} catch (e) {
return {};
}
}
/**
* Return a object of root and leaf dependencies to install
* @returns {Object}
*/
getDependenciesToInstall() {
const rootPkg = this.project.manifest;
const rootSet = /* @__PURE__ */ new Set();
const leaves = /* @__PURE__ */ new Map();
const depsToInstall = /* @__PURE__ */ new Map();
const filteredNodes = new Map(
this.filteredPackages.map((pkg) => [pkg.name, this.targetGraph.get(pkg.name)])
);
const mergedRootDeps = Object.assign(
{},
rootPkg.devDependencies,
rootPkg.optionalDependencies,
rootPkg.dependencies
);
const rootExternalVersions = new Map(
Object.keys(mergedRootDeps).map((externalName) => [externalName, mergedRootDeps[externalName]])
);
rootExternalVersions.forEach((version, externalName) => {
const externalDependents = /* @__PURE__ */ new Set();
const record = /* @__PURE__ */ new Map();
record.set(version, externalDependents);
depsToInstall.set(externalName, record);
});
for (const [leafName, leafNode] of filteredNodes) {
for (const [externalName, resolved] of leafNode.externalDependencies) {
const version = resolved.rawSpec;
const record = depsToInstall.get(externalName) || depsToInstall.set(externalName, /* @__PURE__ */ new Map()).get(externalName);
const externalDependents = record.get(version) || record.set(version, /* @__PURE__ */ new Set()).get(version);
externalDependents.add(leafName);
}
}
const rootActions = [];
const leafActions = [];
let strictExitOnWarning = false;
for (const [externalName, externalDependents] of depsToInstall) {
let rootVersion;
if (this.hoisting && isHoistedPackage(externalName, this.hoisting)) {
const commonVersion = Array.from(externalDependents.keys()).reduce(
(a, b) => externalDependents.get(a).size > externalDependents.get(b).size ? a : b
);
rootVersion = rootExternalVersions.get(externalName) || commonVersion;
if (rootVersion !== commonVersion) {
this.logger.warn(
"EHOIST_ROOT_VERSION",
`The repository root depends on ${externalName}@${rootVersion}, which differs from the more common ${externalName}@${commonVersion}.`
);
if (this.options.strict) {
strictExitOnWarning = true;
}
}
const dependents = Array.from(externalDependents.get(rootVersion)).map(
(leafName) => this.targetGraph.get(leafName).pkg
);
externalDependents.delete(rootVersion);
rootActions.push(
() => hasDependencyInstalled(rootPkg, externalName, rootVersion).then((isSatisfied) => {
rootSet.add({
name: externalName,
dependents,
dependency: `${externalName}@${rootVersion}`,
isSatisfied
});
})
);
}
for (const [leafVersion, leafDependents] of externalDependents) {
for (const leafName of leafDependents) {
if (rootVersion) {
this.logger.warn(
"EHOIST_PKG_VERSION",
`"${leafName}" package depends on ${externalName}@${leafVersion}, which differs from the hoisted ${externalName}@${rootVersion}.`
);
if (this.options.strict) {
strictExitOnWarning = true;
}
}
const leafNode = this.targetGraph.get(leafName);
const leafRecord = leaves.get(leafNode) || leaves.set(leafNode, /* @__PURE__ */ new Set()).get(leafNode);
leafActions.push(
() => hasDependencyInstalled(leafNode.pkg, externalName, leafVersion).then((isSatisfied) => {
leafRecord.add({
dependency: `${externalName}@${leafVersion}`,
isSatisfied
});
})
);
}
}
}
if (this.options.strict && strictExitOnWarning) {
throw new import_core.ValidationError(
"EHOISTSTRICT",
"Package version inconsistencies found while hoisting. Fix the above warnings and retry."
);
}
return (0, import_p_map_series.default)([...rootActions, ...leafActions], (el) => el()).then(() => {
this.logger.silly("root dependencies", JSON.stringify(rootSet, null, 2));
this.logger.silly("leaf dependencies", JSON.stringify(leaves, null, 2));
return { rootSet, leaves };
});
}
/**
* Install external dependencies for all packages
* @returns {Promise}
*/
installExternalDependencies({ leaves, rootSet }) {
const tracker = this.logger.newItem("install dependencies");
const rootPkg = this.project.manifest;
const actions = [];
if (rootSet.size) {
const root = Array.from(rootSet);
actions.push(() => {
const depsToInstallInRoot = root.some(({ isSatisfied }) => !isSatisfied) ? root.map(({ dependency }) => dependency) : [];
if (depsToInstallInRoot.length) {
tracker.info("hoist", "Installing hoisted dependencies into root");
}
const promise = (0, import_core.npmInstallDependencies)(rootPkg, depsToInstallInRoot, this.npmConfig);
return (0, import_core.pulseTillDone)(promise).then(
() => (
// Link binaries into dependent packages so npm scripts will
// have access to them.
(0, import_p_map_series.default)(root, ({ name, dependents }) => {
const { bin } = this.hoistedPackageJson(name);
if (bin) {
return (0, import_p_map.default)(dependents, (pkg) => {
const src = this.hoistedDirectory(name);
return (0, import_core.symlinkBinary)(src, pkg);
});
}
})
)
).then(() => {
tracker.info("hoist", "Finished bootstrapping root");
tracker.completeWork(1);
});
});
actions.push(() => {
const candidates = root.filter((dep) => dep.dependents.length).reduce((list, { name, dependents }) => {
const dirs = dependents.filter((pkg) => pkg.nodeModulesLocation !== rootPkg.nodeModulesLocation).map((pkg) => import_path.default.join(pkg.nodeModulesLocation, name));
return list.concat(dirs);
}, []);
if (!candidates.length) {
tracker.verbose("hoist", "nothing to prune");
tracker.completeWork(1);
return;
}
tracker.info("hoist", "Pruning hoisted dependencies");
tracker.silly("prune", candidates);
tracker.addWork(candidates.length);
return (0, import_p_map.default)(
candidates,
(dirPath) => (0, import_core.pulseTillDone)((0, import_core.rimrafDir)(dirPath)).then(() => {
tracker.verbose("prune", dirPath);
tracker.completeWork(1);
}),
// these are mostly no-ops in the vast majority of cases
{ concurrency: this.concurrency }
).then(() => {
tracker.info("hoist", "Finished pruning hoisted dependencies");
tracker.completeWork(1);
});
});
}
const leafNpmConfig = Object.assign({}, this.npmConfig, {
// Use `npm install --global-style` for leaves when hoisting is enabled
npmGlobalStyle: !!this.options.hoist
});
leaves.forEach((leafRecord, leafNode) => {
const deps = Array.from(leafRecord);
if (deps.some(({ isSatisfied }) => !isSatisfied)) {
actions.push(() => {
const dependencies = deps.map(({ dependency }) => dependency);
const promise = (0, import_core.npmInstallDependencies)(leafNode.pkg, dependencies, leafNpmConfig);
return (0, import_core.pulseTillDone)(promise).then(() => {
tracker.verbose("installed leaf", leafNode.name);
tracker.completeWork(1);
});
});
}
});
if (actions.length) {
tracker.info("", "Installing external dependencies");
tracker.verbose("actions", "%d actions, concurrency %d", actions.length, this.concurrency);
tracker.addWork(actions.length);
}
return (0, import_p_map.default)(actions, (act) => act(), { concurrency: this.concurrency }).finally(() => tracker.finish());
}
/**
* Symlink all packages to the packages/node_modules directory
* Symlink package binaries to dependent packages' node_modules/.bin directory
* @returns {Promise}
*/
symlinkPackages() {
return (0, import_core.symlinkDependencies)(
this.filteredPackages,
this.targetGraph,
this.logger.newItem("bootstrap dependencies")
);
}
}
module.exports.BootstrapCommand = BootstrapCommand;