From 6c7d07a34aee1695ea734ab94a0cefb94e8a0d41 Mon Sep 17 00:00:00 2001 From: David Sheldrick Date: Sun, 27 Jan 2019 19:42:16 +0000 Subject: [PATCH] wip --- package.json | 3 ++ src/PackageDetails.test.ts | 88 +++++++++++++++++++++++-------------- src/PackageDetails.ts | 19 +++++++- src/applyPatches.ts | 45 ++++++++++++++++--- src/detectPackageManager.ts | 4 +- src/makePatch.ts | 26 +++++++---- yarn.lock | 58 ++++++++++++++++++++++++ 7 files changed, 193 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 27097399..ce98fd67 100644 --- a/package.json +++ b/package.json @@ -64,10 +64,13 @@ "typescript": "^3.2.2" }, "dependencies": { + "@types/find-up": "^2.1.1", "@types/is-ci": "^1.1.0", "chalk": "^1.1.3", "cross-spawn": "^5.1.0", + "find-up": "^3.0.0", "fs-extra": "^4.0.1", + "get-yarn-workspaces": "^1.0.2", "is-ci": "^2.0.0", "klaw-sync": "^4.0.0", "minimist": "^1.2.0", diff --git a/src/PackageDetails.test.ts b/src/PackageDetails.test.ts index 6a938331..cd268afe 100644 --- a/src/PackageDetails.test.ts +++ b/src/PackageDetails.test.ts @@ -1,6 +1,7 @@ import { getPackageDetailsFromPatchFilename, getPatchDetailsFromCliString, + resolveNpmRoot, } from "./PackageDetails" describe("getPackageDetailsFromPatchFilename", () => { @@ -124,8 +125,8 @@ Object { describe("getPatchDetailsFromCliString", () => { it("handles a minimal package name", () => { - expect(getPatchDetailsFromCliString("patch-package")).toMatchInlineSnapshot( - ` + expect(getPatchDetailsFromCliString("patch-package")) + .toMatchInlineSnapshot(` Object { "humanReadablePathSpecifier": "patch-package", "isNested": false, @@ -136,15 +137,12 @@ Object { "path": "node_modules/patch-package", "pathSpecifier": "patch-package", } -`, - ) +`) }) it("handles a scoped package name", () => { - expect( - getPatchDetailsFromCliString("@david/patch-package"), - ).toMatchInlineSnapshot( - ` + expect(getPatchDetailsFromCliString("@david/patch-package")) + .toMatchInlineSnapshot(` Object { "humanReadablePathSpecifier": "@david/patch-package", "isNested": false, @@ -155,15 +153,12 @@ Object { "path": "node_modules/@david/patch-package", "pathSpecifier": "@david/patch-package", } -`, - ) +`) }) it("handles a nested package name", () => { - expect( - getPatchDetailsFromCliString("david/patch-package"), - ).toMatchInlineSnapshot( - ` + expect(getPatchDetailsFromCliString("david/patch-package")) + .toMatchInlineSnapshot(` Object { "humanReadablePathSpecifier": "david => patch-package", "isNested": true, @@ -175,15 +170,12 @@ Object { "path": "node_modules/david/node_modules/patch-package", "pathSpecifier": "david/patch-package", } -`, - ) +`) }) it("handles a nested package name with scopes", () => { - expect( - getPatchDetailsFromCliString("@david/patch-package/banana"), - ).toMatchInlineSnapshot( - ` + expect(getPatchDetailsFromCliString("@david/patch-package/banana")) + .toMatchInlineSnapshot(` Object { "humanReadablePathSpecifier": "@david/patch-package => banana", "isNested": true, @@ -195,13 +187,10 @@ Object { "path": "node_modules/@david/patch-package/node_modules/banana", "pathSpecifier": "@david/patch-package/banana", } -`, - ) +`) - expect( - getPatchDetailsFromCliString("@david/patch-package/@david/banana"), - ).toMatchInlineSnapshot( - ` + expect(getPatchDetailsFromCliString("@david/patch-package/@david/banana")) + .toMatchInlineSnapshot(` Object { "humanReadablePathSpecifier": "@david/patch-package => @david/banana", "isNested": true, @@ -213,13 +202,10 @@ Object { "path": "node_modules/@david/patch-package/node_modules/@david/banana", "pathSpecifier": "@david/patch-package/@david/banana", } -`, - ) +`) - expect( - getPatchDetailsFromCliString("david/patch-package/@david/banana"), - ).toMatchInlineSnapshot( - ` + expect(getPatchDetailsFromCliString("david/patch-package/@david/banana")) + .toMatchInlineSnapshot(` Object { "humanReadablePathSpecifier": "david => patch-package => @david/banana", "isNested": true, @@ -232,7 +218,43 @@ Object { "path": "node_modules/david/node_modules/patch-package/node_modules/@david/banana", "pathSpecifier": "david/patch-package/@david/banana", } -`, +`) + }) +}) + +describe("getAbsolutePackagePath", () => { + const resolve = require.resolve + require.resolve = jest.fn(packageName => { + switch (packageName) { + case "chalk": + return "/Users/davidsheldrick/code/patch-package/node_modules/chalk/index.js" + case "typescript": + return "/Users/davidsheldrick/code/patch-package/node_modules/typescript/lib/typescript.js" + } + }) + + it("returns the absolute path for a given package", () => { + expect(resolveNpmRoot(getPatchDetailsFromCliString("chalk")!)).toBe( + "/Users/davidsheldrick/code/patch-package", ) + expect(resolveNpmRoot(getPatchDetailsFromCliString("typescript")!)).toBe( + "/Users/davidsheldrick/code/patch-package", + ) + expect( + resolveNpmRoot(getPatchDetailsFromCliString("typescript/banana")!), + ).toBe("/Users/davidsheldrick/code/patch-package") + expect( + resolveNpmRoot(getPatchDetailsFromCliString("chalk/@types/banana")!), + ).toBe("/Users/davidsheldrick/code/patch-package") + + expect( + resolveNpmRoot( + getPackageDetailsFromPatchFilename("chalk++@types/banana+4.4.4.patch")!, + ), + ).toBe("/Users/davidsheldrick/code/patch-package") + }) + + afterAll(() => { + require.resolve = resolve }) }) diff --git a/src/PackageDetails.ts b/src/PackageDetails.ts index b3af7d37..d18a968b 100644 --- a/src/PackageDetails.ts +++ b/src/PackageDetails.ts @@ -1,4 +1,5 @@ -import { join } from "path" +import { join } from "./path" +import { spawnSafeSync } from "./spawnSafe" interface PackageDetails { humanReadablePathSpecifier: string @@ -133,3 +134,19 @@ export function getPatchDetailsFromCliString( pathSpecifier: specifier, } } + +export function resolveNpmRoot(packageDetails: PackageDetails): string { + const rootPackageName = packageDetails.packageNames[0] + const rootPackageEntryPath = spawnSafeSync("node", [ + "-e", + `console.log(require.resolve(${JSON.stringify(rootPackageName)}))`, + ]).stdout.toString().trim() + const searchString = `/node_modules/${rootPackageName}/` + if (!rootPackageEntryPath.includes(searchString)) { + throw new Error(`Can't find root npm path from '${searchString}'`) + } + return rootPackageEntryPath.slice( + 0, + rootPackageEntryPath.lastIndexOf(searchString), + ) +} diff --git a/src/applyPatches.ts b/src/applyPatches.ts index 352e00a3..ba3eaff6 100644 --- a/src/applyPatches.ts +++ b/src/applyPatches.ts @@ -5,15 +5,19 @@ import { existsSync, readFileSync } from "fs-extra" import { join, resolve } from "./path" import { posix } from "path" import { getPackageDetailsFromPatchFilename } from "./PackageDetails" -import { parsePatchFile } from "./patch/parse" +import { parsePatchFile, ParsedPatchFile } from "./patch/parse" import { reversePatch } from "./patch/reverse" import isCi from "is-ci" +import { assertNever } from "./assertNever" + +// @ts-ignore +import getYarnWorkspaces from "get-yarn-workspaces" // don't want to exit(1) on postinsall locally. // see https://github.com/ds300/patch-package/issues/86 const shouldExitPostinstallWithError = isCi || process.env.NODE_ENV === "test" -function findPatchFiles(patchesDirectory: string): string[] { +function findAllPatchFiles(patchesDirectory: string): string[] { if (!existsSync(patchesDirectory)) { return [] } @@ -47,10 +51,10 @@ function getInstalledPackageVersion({ export const applyPatchesForApp = ( appPath: string, reverse: boolean, - patchDir: string = "patches", + patchDirName: string = "patches", ): void => { - const patchesDirectory = join(appPath, patchDir) - const files = findPatchFiles(patchesDirectory) + const patchesDirectory = join(appPath, patchDirName) + const files = findAllPatchFiles(patchesDirectory) if (files.length === 0) { console.error(red("No patch files found")) @@ -116,12 +120,16 @@ export const applyPatchesForApp = ( }) } -export const applyPatch = ( +const applyPatch = ( patchFilePath: string, reverse: boolean, + npmRoot?: string, ): boolean => { const patchFileContents = readFileSync(patchFilePath).toString() - const patch = parsePatchFile(patchFileContents) + let patch = parsePatchFile(patchFileContents) + if (npmRoot) { + patch = qualifyPathsInPatch(npmRoot, patch) + } try { executeEffects(reverse ? reversePatch(patch) : patch, { dryRun: false }) } catch (e) { @@ -257,3 +265,26 @@ ${red.bold("**ERROR**")} ${red( Installed version: ${red.bold(actualVersion)} `) } + +function qualifyPathsInPatch( + path: string, + patch: ParsedPatchFile, +): ParsedPatchFile { + return patch.map(eff => { + switch (eff.type) { + case "file creation": + case "file deletion": + case "mode change": + case "patch": + return { ...eff, path: join(path, eff.path) } + case "rename": + return { + ...eff, + fromPath: join(path, eff.fromPath), + toPath: join(path, eff.toPath), + } + default: + return assertNever(eff) + } + }) +} diff --git a/src/detectPackageManager.ts b/src/detectPackageManager.ts index 1bfe07a1..442f7392 100644 --- a/src/detectPackageManager.ts +++ b/src/detectPackageManager.ts @@ -2,6 +2,7 @@ import fs from "fs-extra" import { join } from "./path" import chalk from "chalk" import process from "process" +import findUp = require("find-up") export type PackageManager = "yarn" | "npm" | "npm-shrinkwrap" @@ -46,7 +47,8 @@ export const detectPackageManager = ( const shrinkWrapExists = fs.existsSync( join(appRootPath, "npm-shrinkwrap.json"), ) - const yarnLockExists = fs.existsSync(join(appRootPath, "yarn.lock")) + const yarnLockExists = findUp.sync("yarn.lock", { cwd: appRootPath }) + if ((packageLockExists || shrinkWrapExists) && yarnLockExists) { if (overridePackageManager) { return overridePackageManager diff --git a/src/makePatch.ts b/src/makePatch.ts index 46ed2ed7..67d8c318 100644 --- a/src/makePatch.ts +++ b/src/makePatch.ts @@ -1,5 +1,5 @@ -import { green, grey } from "chalk" -import { join, dirname, resolve } from "./path" +import { green, grey, cyan } from "chalk" +import { join, dirname, resolve, relative } from "./path" import { spawnSafeSync } from "./spawnSafe" import { PackageManager } from "./detectPackageManager" import { removeIgnoredFiles } from "./filterFiles" @@ -17,6 +17,7 @@ import { getPatchFiles } from "./patchFs" import { getPatchDetailsFromCliString, getPackageDetailsFromPatchFilename, + resolveNpmRoot, } from "./PackageDetails" function printNoPackageFoundError( @@ -36,7 +37,7 @@ export const makePatch = ( packageManager: PackageManager, includePaths: RegExp, excludePaths: RegExp, - patchDir: string = "patches", + patchDirName: string = "patches", ) => { const packageDetails = getPatchDetailsFromCliString(packagePathSpecifier) @@ -45,9 +46,13 @@ export const makePatch = ( return } const appPackageJson = require(join(appPath, "package.json")) - const packagePath = join(appPath, packageDetails.path) + const npmRoot = resolveNpmRoot(packageDetails) + const packagePath = join(npmRoot, packageDetails.path) const packageJsonPath = join(packagePath, "package.json") + console.log("Patching chalk at", cyan(relative(process.cwd(), packagePath))) + console.log("app path", appPath) + if (!existsSync(packageJsonPath)) { printNoPackageFoundError(packagePathSpecifier, packageJsonPath) process.exit(1) @@ -87,7 +92,7 @@ export const makePatch = ( const tmpRepoPackageJsonPath = join(tmpRepoNpmRoot, "package.json") try { - const patchesDir = join(appPath, patchDir) + const patchesDir = join(npmRoot, patchDirName) console.info(grey("•"), "Creating temporary folder") @@ -179,10 +184,10 @@ export const makePatch = ( .join("++") // maybe delete existing - getPatchFiles(patchDir).forEach(filename => { + getPatchFiles(patchesDir).forEach(filename => { const deets = getPackageDetailsFromPatchFilename(filename) if (deets && deets.path === packageDetails.path) { - unlinkSync(join(patchDir, filename)) + unlinkSync(join(patchesDir, filename)) } }) @@ -194,7 +199,12 @@ export const makePatch = ( mkdirSync(dirname(patchPath)) } writeFileSync(patchPath, diffResult.stdout) - console.log(`${green("✔")} Created file ${patchDir}/${patchFileName}`) + console.log( + `${green("✔")} Created file ${relative( + process.cwd(), + patchesDir, + )}/${patchFileName}`, + ) } } catch (e) { console.error(e) diff --git a/yarn.lock b/yarn.lock index b36a6a60..02e9aed9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,6 +26,11 @@ version "0.4.31" resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9" +"@types/find-up@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/find-up/-/find-up-2.1.1.tgz#1cd2d240f1ad1f48d32346074724dc3107248a11" + integrity sha512-60LC501bQRN9/3yfVaEEMd7IndaufffL56PBRAejPpUrY304Ps1jfnjNqPw5jmM5R8JHWiKBAe5IHzNcPV41AA== + "@types/fs-extra@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.0.tgz#1dd742ad5c9bce308f7a52d02ebc01421bc9102f" @@ -1364,6 +1369,11 @@ find-parent-dir@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -1377,12 +1387,24 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + findup-sync@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16" dependencies: glob "~5.0.0" +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -1483,6 +1505,15 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +get-yarn-workspaces@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-yarn-workspaces/-/get-yarn-workspaces-1.0.2.tgz#81591bdb392f1c6bac09cdc8491a6d275781aa44" + integrity sha512-Auel048Uclfgr74oNXKZWH30UgKDZXQdfUfgD9iWXdoUGJpeWg9lSuX/FZkQ6RB3KnBfAaf70xQXfwOjiE9rPw== + dependencies: + find-root "^1.1.0" + flatten "^1.0.2" + glob "^7.1.2" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2748,6 +2779,14 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -3293,16 +3332,35 @@ p-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" +p-limit@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68" + integrity sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + p-map@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== + package-json@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"