From 72296300de832acfa4888f969e6679d9a45bb635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20H=C3=B8egh?= Date: Mon, 15 Apr 2019 16:35:06 +0200 Subject: [PATCH] Add lazy flag --- bin/lernaupdate | 5 ++ package.json | 1 + src/index.js | 91 +++++++++++++++++++++++------ src/utils/modifyPackageJson.js | 7 +++ src/utils/modifyPackageJson.spec.js | 81 +++++++++++++++++++++++++ test/lazy.flag.spec.js | 67 +++++++++++++++++++++ 6 files changed, 234 insertions(+), 18 deletions(-) create mode 100644 src/utils/modifyPackageJson.js create mode 100644 src/utils/modifyPackageJson.spec.js create mode 100644 test/lazy.flag.spec.js diff --git a/bin/lernaupdate b/bin/lernaupdate index 4b2ed13..d9d7ab7 100755 --- a/bin/lernaupdate +++ b/bin/lernaupdate @@ -20,6 +20,8 @@ const { input, flags } = meow( --non-interactive Prevent any interactive prompts (Requires --dependency flag to be specified) + --lazy Runs a single install command after updating package.json files, instead installing inside each individual package (Useful for workspaces) + Examples: $ lernaupdate ./myproject --dedupe @@ -50,6 +52,9 @@ const { input, flags } = meow( newIntallsMode: { type: "string", }, + lazy: { + type: "boolean", + }, }, } ); diff --git a/package.json b/package.json index 5603c1d..e5cbed4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "lodash": "^4.17.4", "meow": "^4.0.0", "minimist": "^1.2.0", + "prettier-package-json": "^2.1.0", "semver-compare": "^1.0.0" }, "devDependencies": { diff --git a/src/index.js b/src/index.js index 46dfffb..5b4fa11 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ const orderBy = require("lodash/orderBy"); const globby = require("globby"); const semverCompare = require("semver-compare"); const perf = require("execution-time")(); +const fs = require("fs-extra"); const runCommand = require("./utils/runCommand"); const fileExists = require("./utils/fileExists"); @@ -14,6 +15,7 @@ const plural = require("./utils/plural"); const invariant = require("./utils/invariant"); const parseDependency = require("./utils/parseDependency"); const sanitizeGitBranchName = require("./utils/sanitizeGitBranchName"); +const modifyPackageJson = require("./utils/modifyPackageJson"); inquirer.registerPrompt( "autocomplete", @@ -353,22 +355,42 @@ module.exports = async ({ input, flags }) => { targetVersion = promptedTarget; } + let targetVersionResolved = targetVersion; + + let targetVersionLookup = (await runCommand( + `npm info ${targetDependency}@${targetVersion} version`, + { + startMessage: `Resolving dependency version for "${targetDependency}@${targetVersion}"`, + logOutput: false, + } + )).trim(); + + invariant( + targetVersionLookup, + `The version "${targetVersion}" was not found for "${targetDependency}"` + ); + + // If targeting a specific tag (such as @latest), + const versionFromDistTag = npmPackageInfo["dist-tags"][targetVersion]; + if (versionFromDistTag) { + targetVersionResolved = `^${versionFromDistTag}`; + } + + ui.log.write(chalk.green(`Using version ${targetVersionResolved} ✓\n`)); perf.start(); let totalInstalls = 0; + const dependencyManager = (await fileExists(resolve(projectDir, "yarn.lock"))) + ? "yarn" + : "npm"; + // Install process for (let depName of targetPackages) { const existingDependency = dependencyMap[targetDependency]; let source = "dependencies"; - const dependencyManager = (await fileExists( - resolve(projectDir, "yarn.lock") - )) - ? "yarn" - : "npm"; - if (existingDependency && existingDependency.packs[depName]) { const { version, source: theSource } = existingDependency.packs[depName] || {}; @@ -406,9 +428,10 @@ module.exports = async ({ input, flags }) => { }[flags.newInstallsMode]; } - const { path: packageDir } = packages.find( - ({ config: { name } }) => name === depName - ); + const { + path: packageDir, + config: { name: packageName }, + } = packages.find(({ config: { name } }) => name === depName); const sourceParam = { yarn: { @@ -421,20 +444,52 @@ module.exports = async ({ input, flags }) => { }, }[dependencyManager][source || "dependencies"]; - const installCmd = (dependencyManager === "yarn" - ? ["yarn", "add", sourceParam, `${targetDependency}@${targetVersion}`] - : ["npm", "install", sourceParam, `${targetDependency}@${targetVersion}`] - ).join(" "); + if (flags.lazy) { + const packageJsonPath = resolve(packageDir, "package.json"); + const targetPackageJson = require(packageJsonPath); - await runCommand(`cd ${packageDir} && ${installCmd}`, { - startMessage: `${chalk.white.bold(depName)}: ${installCmd}`, - endMessage: chalk.green(`${depName} ✓`), - logTime: true, - }); + fs.writeFileSync( + packageJsonPath, + modifyPackageJson(targetPackageJson, { + [source]: { [targetDependency]: targetVersionResolved }, + }) + ); + ui.log.write( + chalk`{white.bold ${packageName}}: {green package.json updated ✓}\n` + ); + } else { + const installCmd = (dependencyManager === "yarn" + ? ["yarn", "add", sourceParam, `${targetDependency}@${targetVersion}`] + : [ + "npm", + "install", + sourceParam, + `${targetDependency}@${targetVersion}`, + ] + ).join(" "); + + await runCommand(`cd ${packageDir} && ${installCmd}`, { + startMessage: `${chalk.white.bold(depName)}: ${installCmd}`, + endMessage: chalk.green(`${depName} ✓`), + logTime: true, + }); + } totalInstalls++; } + if (flags.lazy) { + ui.log.write(""); + + const installCmd = dependencyManager === "yarn" ? "yarn" : "npm install"; + + await runCommand(`cd ${projectDir} && ${installCmd}`, { + startMessage: `${chalk.white.bold(projectName)}: ${installCmd}`, + endMessage: chalk.green(`Packages installed ✓`), + logTime: true, + }); + } + if (totalInstalls === 0) process.exit(); ui.log.write( diff --git a/src/utils/modifyPackageJson.js b/src/utils/modifyPackageJson.js new file mode 100644 index 0000000..af341ec --- /dev/null +++ b/src/utils/modifyPackageJson.js @@ -0,0 +1,7 @@ +const { format } = require("prettier-package-json"); +const merge = require("lodash/merge"); + +module.exports = (basePackageJson, modificationsBlob) => { + const merged = merge(basePackageJson, modificationsBlob); + return format(merged); +}; diff --git a/src/utils/modifyPackageJson.spec.js b/src/utils/modifyPackageJson.spec.js new file mode 100644 index 0000000..d197b83 --- /dev/null +++ b/src/utils/modifyPackageJson.spec.js @@ -0,0 +1,81 @@ +const expect = require("unexpected"); +const modifyPackageJson = require("./modifyPackageJson"); + +const baseJson = JSON.parse(`{ + "name": "lerna-update-wizard", + "bin": { + "lernaupdate": "./bin/lernaupdate" + }, + "version": "0.12.0", + "main": "index.js", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Anifacted/lerna-update-wizard" + }, + "dependencies": { + "chalk": "^2.3.0", + "semver-compare": "^1.0.0" + }, + "devDependencies": { + }, + "scripts": { + "test": "jest --verbose --runInBand" + }, + "jest": { + "testURL": "http://localhost" + } +}`); + +describe("modifyPackageJson", () => { + it("should work", () => { + const mods = { + dependencies: { + d3: "3", + }, + devDependencies: { + lodash: "3", + }, + peerDependencies: { + underscore: "~2", + }, + }; + + expect( + modifyPackageJson, + "when called with", + [baseJson, mods], + "to equal", + `{ + "name": "lerna-update-wizard", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Anifacted/lerna-update-wizard" + }, + "version": "0.12.0", + "main": "index.js", + "bin": { + "lernaupdate": "./bin/lernaupdate" + }, + "scripts": { + "test": "jest --verbose --runInBand" + }, + "dependencies": { + "chalk": "^2.3.0", + "d3": "3", + "semver-compare": "^1.0.0" + }, + "peerDependencies": { + "underscore": "~2" + }, + "devDependencies": { + "lodash": "3" + }, + "jest": { + "testURL": "http://localhost" + } +}\n` + ); + }); +}); diff --git a/test/lazy.flag.spec.js b/test/lazy.flag.spec.js new file mode 100644 index 0000000..583a91a --- /dev/null +++ b/test/lazy.flag.spec.js @@ -0,0 +1,67 @@ +const { default: runProgram } = require("./utils/runProgram"); +const generateProject = require("./utils/generateProject"); + +describe("Lazy install dependency", async () => { + describe("dep is not installed in any of the packages", () => { + it("lazily installs the dependency", async () => { + // eslint-disable-next-line + jest.setTimeout(100000); + + const projectPath = await generateProject({ + name: "project-a", + packages: [ + { name: "sub-package-a" }, + { + name: "sub-package-b", + dependencies: { lodash: "0.1.0" }, + }, + ], + }); + + await runProgram( + `${projectPath} --lazy --dependency "treediff@latest"`, + ` + Fetching package information for "treediff" + + >>> wait + + ? Select packages to affect: (Press to select, to toggle all, to + invert selection) + ❯◯ sub-package-a + ◯ sub-package-b + + >>> input SPACE + >>> input ENTER + + Resolving dependency version for "treediff@latest" + + >>> wait + + Using version ^0.2.5 ✓ + + >>> wait + + ? Select dependency installation type for "sub-package-a" (Use arrow keys) + ❯ dependencies + devDependencies + + >>> input ENTER + + project-a: npm install + + >>> wait + + ? Do you want to create a new git branch for the change? (Y/n) + + >>> input n + >>> input ENTER + + ? Do you want to create a new git commit for the change? (Y/n) + + >>> input n + >>> input ENTER + ` + ); + }); + }); +});