UNPKG

@lerna/publish

Version:

Publish packages in the current project

423 lines (422 loc) 17.9 kB
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;