From 47b5e45dd6f5eca91a5cedd059aed0f3a7e73be9 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 20 Jun 2022 16:00:24 +0300 Subject: [PATCH] feat: add `patch` and `patch-commit` commands (#4900) close #3077 close #2675 --- .changeset/chilly-hornets-type.md | 5 + .changeset/tame-beans-hang.md | 9 ++ .changeset/tough-hats-drop.md | 31 +++++ .meta-updater/src/index.ts | 1 + packages/config/src/Config.ts | 2 +- .../patch-pkg/is-positive@1.0.0.patch | 1 - packages/core/test/install/patch.ts | 2 +- packages/create-cafs-store/src/index.ts | 4 +- packages/fetcher-base/src/index.ts | 2 +- packages/lifecycle/package.json | 1 + packages/lifecycle/src/index.ts | 12 +- .../src/storeController/index.ts | 2 +- packages/plugin-commands-patching/README.md | 13 ++ .../plugin-commands-patching/jest.config.js | 4 + .../plugin-commands-patching/package.json | 63 +++++++++ .../plugin-commands-patching/src/index.ts | 4 + .../plugin-commands-patching/src/patch.ts | 75 +++++++++++ .../src/patchCommit.ts | 95 ++++++++++++++ .../test/patch.test.ts | 41 ++++++ .../plugin-commands-patching/tsconfig.json | 40 ++++++ .../tsconfig.lint.json | 8 ++ .../plugin-commands-publishing/package.json | 2 +- packages/plugin-commands-rebuild/package.json | 2 +- .../package.json | 2 +- packages/plugin-commands-store/package.json | 2 +- packages/pnpm/package.json | 3 +- packages/pnpm/src/cmd/index.ts | 3 + packages/pnpm/tsconfig.json | 3 + pnpm-lock.yaml | 122 ++++++++++++++++-- typings/local.d.ts | 4 + 30 files changed, 534 insertions(+), 24 deletions(-) create mode 100644 .changeset/chilly-hornets-type.md create mode 100644 .changeset/tame-beans-hang.md create mode 100644 .changeset/tough-hats-drop.md create mode 100644 packages/plugin-commands-patching/README.md create mode 100644 packages/plugin-commands-patching/jest.config.js create mode 100644 packages/plugin-commands-patching/package.json create mode 100644 packages/plugin-commands-patching/src/index.ts create mode 100644 packages/plugin-commands-patching/src/patch.ts create mode 100644 packages/plugin-commands-patching/src/patchCommit.ts create mode 100644 packages/plugin-commands-patching/test/patch.test.ts create mode 100644 packages/plugin-commands-patching/tsconfig.json create mode 100644 packages/plugin-commands-patching/tsconfig.lint.json diff --git a/.changeset/chilly-hornets-type.md b/.changeset/chilly-hornets-type.md new file mode 100644 index 000000000..b62c67ab7 --- /dev/null +++ b/.changeset/chilly-hornets-type.md @@ -0,0 +1,5 @@ +--- +"@pnpm/plugin-commands-patching": major +--- + +Initial release. diff --git a/.changeset/tame-beans-hang.md b/.changeset/tame-beans-hang.md new file mode 100644 index 000000000..0b3e69458 --- /dev/null +++ b/.changeset/tame-beans-hang.md @@ -0,0 +1,9 @@ +--- +"@pnpm/config": minor +"@pnpm/create-cafs-store": minor +"@pnpm/fetcher-base": minor +"@pnpm/package-store": minor +"pnpm": minor +--- + +`package-import-method` supports a new option: `clone-or-copy`. diff --git a/.changeset/tough-hats-drop.md b/.changeset/tough-hats-drop.md new file mode 100644 index 000000000..d3eef8a6a --- /dev/null +++ b/.changeset/tough-hats-drop.md @@ -0,0 +1,31 @@ +--- +"pnpm": minor +--- + +Two new commands added: `pnpm patch` and `pnpm patch-commit`. + +`pnpm patch ` prepares a package for patching. For instance, if you want to patch express v1, run: + +``` +pnpm patch express@1.0.0 +``` + +pnpm will create a temporary directory with `express@1.0.0` that you can modify with your changes. +Once you are read with your changes, run: + +``` +pnpm patch-commit +``` + +This will create a patch file and write it to `/patches/express@1.0.0.patch`. +Also, it will reference this new patch file from the `patchedDependencies` field in `package.json`: + +```json +{ + "pnpm": { + "patchedDependencies": { + "express@1.0.0": "patches/express@1.0.0.patch" + } + } +} +``` diff --git a/.meta-updater/src/index.ts b/.meta-updater/src/index.ts index b604651be..cac674dfe 100644 --- a/.meta-updater/src/index.ts +++ b/.meta-updater/src/index.ts @@ -132,6 +132,7 @@ async function updateManifest (workspaceDir: string, manifest: ProjectManifest, case '@pnpm/plugin-commands-installation': case '@pnpm/plugin-commands-listing': case '@pnpm/plugin-commands-outdated': + case '@pnpm/plugin-commands-patching': case '@pnpm/plugin-commands-publishing': case '@pnpm/plugin-commands-rebuild': case '@pnpm/plugin-commands-script-runners': diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 87e0b1c6e..d7daeaaf7 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -112,7 +112,7 @@ export interface Config { ignorePnpmfile?: boolean pnpmfile: string hooks?: Hooks - packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' + packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' hoistPattern?: string[] publicHoistPattern?: string[] useStoreServer?: boolean diff --git a/packages/core/test/fixtures/patch-pkg/is-positive@1.0.0.patch b/packages/core/test/fixtures/patch-pkg/is-positive@1.0.0.patch index 7cef18ba0..53bee08fd 100644 --- a/packages/core/test/fixtures/patch-pkg/is-positive@1.0.0.patch +++ b/packages/core/test/fixtures/patch-pkg/is-positive@1.0.0.patch @@ -9,4 +9,3 @@ index 8e020ca..ff3aee4 100644 + // patched return n >= 0; }; - diff --git a/packages/core/test/install/patch.ts b/packages/core/test/install/patch.ts index 809a14ad3..98b1e071d 100644 --- a/packages/core/test/install/patch.ts +++ b/packages/core/test/install/patch.ts @@ -37,7 +37,7 @@ test('patch package', async () => { const filesIndexFile = path.join(opts.storeDir, 'files/c7/1ccf199e0fdae37aad13946b937d67bcd35fa111b84d21b3a19439cfdc2812c5d8da8a735e94c2a1ccb77b4583808ee8405313951e7146ac83ede3671dc292-index.json') const filesIndex = await loadJsonFile(filesIndexFile) - const sideEffectsKey = `${ENGINE_NAME}-{}-meyqmf5tej4bwn3gxydpfig6pe` + const sideEffectsKey = `${ENGINE_NAME}-{}-jnbpamcxayl5i4ehrkoext3any` const patchedFileIntegrity = filesIndex.sideEffects?.[sideEffectsKey]['index.js']?.integrity expect(patchedFileIntegrity).toBeTruthy() const originalFileIntegrity = filesIndex.files['index.js'].integrity diff --git a/packages/create-cafs-store/src/index.ts b/packages/create-cafs-store/src/index.ts index 617e011be..b85d808a8 100644 --- a/packages/create-cafs-store/src/index.ts +++ b/packages/create-cafs-store/src/index.ts @@ -14,7 +14,7 @@ import createImportPackage from './createImportPackage' function createPackageImporter ( opts: { - packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' + packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' cafsDir: string } ): ImportPackageFunction { @@ -63,7 +63,7 @@ export default function createCafsStore ( storeDir: string, opts?: { ignoreFile?: (filename: string) => boolean - packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' + packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' } ) { const cafsDir = path.join(storeDir, 'files') diff --git a/packages/fetcher-base/src/index.ts b/packages/fetcher-base/src/index.ts index a9e966c7d..695db3747 100644 --- a/packages/fetcher-base/src/index.ts +++ b/packages/fetcher-base/src/index.ts @@ -11,7 +11,7 @@ export interface PackageFileInfo { export type PackageFilesResponse = { fromStore: boolean - packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' + packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' sideEffects?: Record> } & ({ local: true diff --git a/packages/lifecycle/package.json b/packages/lifecycle/package.json index 326c515f6..7fec40e71 100644 --- a/packages/lifecycle/package.json +++ b/packages/lifecycle/package.json @@ -41,6 +41,7 @@ "@pnpm/read-package-json": "workspace:6.0.4", "@pnpm/store-controller-types": "workspace:13.0.4", "@pnpm/types": "workspace:8.2.0", + "patch-package": "^6.4.7", "path-exists": "^4.0.0", "run-groups": "^3.0.1" }, diff --git a/packages/lifecycle/src/index.ts b/packages/lifecycle/src/index.ts index 067e2063a..b20720867 100644 --- a/packages/lifecycle/src/index.ts +++ b/packages/lifecycle/src/index.ts @@ -3,6 +3,7 @@ import { safeReadPackageFromDir } from '@pnpm/read-package-json' import exists from 'path-exists' import runLifecycleHook, { RunLifecycleHookOptions } from './runLifecycleHook' import runLifecycleHooksConcurrently, { RunLifecycleHooksConcurrentlyOptions } from './runLifecycleHooksConcurrently' +import { applyPatch } from 'patch-package/dist/applyPatches' export function makeNodeRequireOption (modulePath: string) { let { NODE_OPTIONS } = process.env @@ -26,8 +27,15 @@ export async function runPostinstallHooks ( pkg.scripts = {} } if (opts.patchPath) { - pkg.scripts['pnpm:patch'] = `git apply ${opts.patchPath}` - await runLifecycleHook('pnpm:patch', pkg, opts) + // Ideally, we would just run "patch" or "git apply". + // However, "patch" is not available on Windows and "git apply" is hard to execute on a subdirectory of an existing repository + const cwd = process.cwd() + process.chdir(opts.pkgRoot) + applyPatch({ + patchFilePath: opts.patchPath, + patchDir: opts.pkgRoot, + }) + process.chdir(cwd) } if (!pkg.scripts.install) { diff --git a/packages/package-store/src/storeController/index.ts b/packages/package-store/src/storeController/index.ts index 6c34710a2..d9fadf244 100644 --- a/packages/package-store/src/storeController/index.ts +++ b/packages/package-store/src/storeController/index.ts @@ -24,7 +24,7 @@ export default async function ( ignoreFile?: (filename: string) => boolean storeDir: string networkConcurrency?: number - packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' + packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' verifyStoreIntegrity: boolean } ): Promise { diff --git a/packages/plugin-commands-patching/README.md b/packages/plugin-commands-patching/README.md new file mode 100644 index 000000000..4bf48ea36 --- /dev/null +++ b/packages/plugin-commands-patching/README.md @@ -0,0 +1,13 @@ +# @pnpm/plugin-commands-patches + +> Commands for creating patches + +## Installation + +```sh +pnpm add @pnpm/plugin-commands-patches +``` + +## License + +MIT diff --git a/packages/plugin-commands-patching/jest.config.js b/packages/plugin-commands-patching/jest.config.js new file mode 100644 index 000000000..2ddfb5f73 --- /dev/null +++ b/packages/plugin-commands-patching/jest.config.js @@ -0,0 +1,4 @@ +const config = require('../../jest.config.js') + +module.exports = config + diff --git a/packages/plugin-commands-patching/package.json b/packages/plugin-commands-patching/package.json new file mode 100644 index 000000000..7bc58301c --- /dev/null +++ b/packages/plugin-commands-patching/package.json @@ -0,0 +1,63 @@ +{ + "name": "@pnpm/plugin-commands-patching", + "version": "0.0.0", + "description": "Commands for creating patches", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "!*.map" + ], + "engines": { + "node": ">=14.6" + }, + "scripts": { + "lint": "eslint src/**/*.ts test/**/*.ts", + "registry-mock": "registry-mock", + "test:jest": "jest", + "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7776 pnpm run test:e2e", + "test": "pnpm run compile && pnpm run _test", + "prepublishOnly": "pnpm run compile", + "compile": "tsc --build && pnpm run lint --fix" + }, + "repository": "https://github.com/pnpm/pnpm/blob/main/packages/plugin-commands-patching", + "keywords": [ + "pnpm7", + "pnpm", + "scripts" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "homepage": "https://github.com/pnpm/pnpm/blob/main/packages/plugin-commands-patching#readme", + "devDependencies": { + "@pnpm/logger": "^4.0.0", + "@pnpm/plugin-commands-patching": "workspace:0.0.0", + "@pnpm/prepare": "workspace:*", + "@pnpm/registry-mock": "2.20.0", + "@types/ramda": "0.27.39" + }, + "dependencies": { + "@pnpm/cli-utils": "workspace:0.7.13", + "@pnpm/config": "workspace:15.3.0", + "@pnpm/parse-wanted-dependency": "workspace:3.0.0", + "@pnpm/pick-registry-for-package": "workspace:3.0.3", + "@pnpm/read-package-json": "workspace:6.0.4", + "@pnpm/read-project-manifest": "workspace:3.0.4", + "@pnpm/store-connection-manager": "workspace:4.1.11", + "@pnpm/store-path": "workspace:6.0.0", + "escape-string-regexp": "^4.0.0", + "ramda": "^0.27.1", + "render-help": "^1.0.1", + "safe-execa": "^0.1.1" + }, + "peerDependencies": { + "@pnpm/logger": "^4.0.0" + }, + "funding": "https://opencollective.com/pnpm", + "exports": { + ".": "./lib/index.js" + } +} diff --git a/packages/plugin-commands-patching/src/index.ts b/packages/plugin-commands-patching/src/index.ts new file mode 100644 index 000000000..0752d4557 --- /dev/null +++ b/packages/plugin-commands-patching/src/index.ts @@ -0,0 +1,4 @@ +import * as patch from './patch' +import * as patchCommit from './patchCommit' + +export { patch, patchCommit } diff --git a/packages/plugin-commands-patching/src/patch.ts b/packages/plugin-commands-patching/src/patch.ts new file mode 100644 index 000000000..9274b4582 --- /dev/null +++ b/packages/plugin-commands-patching/src/patch.ts @@ -0,0 +1,75 @@ +import path from 'path' +import { docsUrl } from '@pnpm/cli-utils' +import { Config, types as allTypes } from '@pnpm/config' +import { LogBase } from '@pnpm/logger' +import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager' +import parseWantedDependency from '@pnpm/parse-wanted-dependency' +import storePath from '@pnpm/store-path' +import pick from 'ramda/src/pick' +import pickRegistryForPackage from '@pnpm/pick-registry-for-package' +import renderHelp from 'render-help' + +export const rcOptionsTypes = cliOptionsTypes + +export function cliOptionsTypes () { + return pick([], allTypes) +} + +export const commandNames = ['patch'] + +export function help () { + return renderHelp({ + description: 'Prepare a package for patching', + descriptionLists: [], + url: docsUrl('patch'), + usages: ['pnpm patch'], + }) +} + +export type PatchCommandOptions = Pick & CreateStoreControllerOptions & { + reporter?: (logObj: LogBase) => void +} + +export async function handler (opts: PatchCommandOptions, params: string[]) { + const store = await createOrConnectStoreController({ + ...opts, + packageImportMethod: 'clone-or-copy', + }) + const dep = parseWantedDependency(params[0]) + const pkgResponse = await store.ctrl.requestPackage(dep, { + downloadPriority: 1, + lockfileDir: opts.dir, + preferredVersions: {}, + projectDir: opts.dir, + registry: (dep.alias && pickRegistryForPackage(opts.registries, dep.alias)) ?? opts.registries.default, + }) + const filesResponse = await pkgResponse.files!() + const tempDir = path.join(await getStoreTempDir(opts), Math.random().toString()) + const userChangesDir = path.join(tempDir, 'user') + await Promise.all([ + store.ctrl.importPackage(path.join(tempDir, 'source'), { + filesResponse, + force: true, + }), + store.ctrl.importPackage(userChangesDir, { + filesResponse, + force: true, + }), + ]) + return `You can now edit the following folder: ${userChangesDir}` +} + +async function getStoreTempDir ( + opts: { + dir: string + storeDir?: string + pnpmHomeDir: string + } +): Promise { + const storeDir = await storePath({ + pkgRoot: opts.dir, + storePath: opts.storeDir, + pnpmHomeDir: opts.pnpmHomeDir, + }) + return path.join(storeDir, 'tmp') +} diff --git a/packages/plugin-commands-patching/src/patchCommit.ts b/packages/plugin-commands-patching/src/patchCommit.ts new file mode 100644 index 000000000..ad93fba7e --- /dev/null +++ b/packages/plugin-commands-patching/src/patchCommit.ts @@ -0,0 +1,95 @@ +import fs from 'fs' +import path from 'path' +import { docsUrl } from '@pnpm/cli-utils' +import { types as allTypes } from '@pnpm/config' +import { fromDir as readPackageJsonFromDir } from '@pnpm/read-package-json' +import { tryReadProjectManifest } from '@pnpm/read-project-manifest' +import pick from 'ramda/src/pick' +import execa from 'safe-execa' +import escapeStringRegexp from 'escape-string-regexp' +import renderHelp from 'render-help' + +export const rcOptionsTypes = cliOptionsTypes + +export function cliOptionsTypes () { + return pick([], allTypes) +} + +export const commandNames = ['patch-commit'] + +export function help () { + return renderHelp({ + description: 'Generate a patch out of a directory', + descriptionLists: [], + url: docsUrl('patch-commit'), + usages: ['pnpm patch-commit '], + }) +} + +export async function handler (opts: { dir: string, lockfileDir?: string }, params: string[]) { + const userDir = params[0] + const srcDir = path.join(userDir, '../source') + const patchContent = await diffFolders(srcDir, userDir) + const lockfileDir = opts.lockfileDir ?? opts.dir ?? process.cwd() + const patchesDir = path.join(lockfileDir, 'patches') + await fs.promises.mkdir(patchesDir, { recursive: true }) + const patchedPkgManifest = await readPackageJsonFromDir(srcDir) + const pkgNameAndVersion = `${patchedPkgManifest.name}@${patchedPkgManifest.version}` + await fs.promises.writeFile(path.join(patchesDir, `${pkgNameAndVersion}.patch`), patchContent, 'utf8') + let { manifest, writeProjectManifest } = await tryReadProjectManifest(lockfileDir) + if (!manifest) { + manifest = {} + } + if (!manifest.pnpm) { + manifest.pnpm = { + patchedDependencies: {}, + } + } else if (!manifest.pnpm.patchedDependencies) { + manifest.pnpm.patchedDependencies = {} + } + manifest.pnpm.patchedDependencies![pkgNameAndVersion] = `patches/${pkgNameAndVersion}.patch` + await writeProjectManifest(manifest) +} + +async function diffFolders (folderA: string, folderB: string) { + const folderAN = folderA.replace(/\\/g, '/') + const folderBN = folderB.replace(/\\/g, '/') + let stdout!: string + let stderr!: string + + try { + const result = await execa('git', ['-c', 'core.safecrlf=false', 'diff', '--src-prefix=a/', '--dst-prefix=b/', '--ignore-cr-at-eol', '--full-index', '--no-index', '--text', folderAN, folderBN], { + cwd: process.cwd(), + env: { + ...process.env, + // #region Predictable output + // These variables aim to ignore the global git config so we get predictable output + // https://git-scm.com/docs/git#Documentation/git.txt-codeGITCONFIGNOSYSTEMcode + GIT_CONFIG_NOSYSTEM: '1', + HOME: '', + XDG_CONFIG_HOME: '', + USERPROFILE: '', + // #endregion + }, + }) + stdout = result.stdout + stderr = result.stderr + } catch (err: any) { // eslint-disable-line + stdout = err.stdout + stderr = err.stderr + } + // we cannot rely on exit code, because --no-index implies --exit-code + // i.e. git diff will exit with 1 if there were differences + if (stderr.length > 0) + throw new Error(`Unable to diff directories. Make sure you have a recent version of 'git' available in PATH.\nThe following error was reported by 'git':\n${stderr}`) + + const normalizePath = folderAN.startsWith('/') + ? (p: string) => p.slice(1) + : (p: string) => p + + return stdout + .replace(new RegExp(`(a|b)(${escapeStringRegexp(`/${normalizePath(folderAN)}/`)})`, 'g'), '$1/') + .replace(new RegExp(`(a|b)${escapeStringRegexp(`/${normalizePath(folderBN)}/`)}`, 'g'), '$1/') + .replace(new RegExp(escapeStringRegexp(`${folderAN}/`), 'g'), '') + .replace(new RegExp(escapeStringRegexp(`${folderBN}/`), 'g'), '') +} diff --git a/packages/plugin-commands-patching/test/patch.test.ts b/packages/plugin-commands-patching/test/patch.test.ts new file mode 100644 index 000000000..a0cb6743a --- /dev/null +++ b/packages/plugin-commands-patching/test/patch.test.ts @@ -0,0 +1,41 @@ +import fs from 'fs' +import path from 'path' +import prepare from '@pnpm/prepare' +import { patch, patchCommit } from '@pnpm/plugin-commands-patching' +import readProjectManifest from '@pnpm/read-project-manifest' +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' + +test('patch and commit', async () => { + prepare() + const cacheDir = path.resolve('cache') + const storeDir = path.resolve('store') + + await patch.handler({ + cacheDir, + dir: process.cwd(), + pnpmHomeDir: '', + rawConfig: { + registry: `http://localhost:${REGISTRY_MOCK_PORT}/`, + }, + registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` }, + storeDir, + userConfig: {}, + }, ['is-positive@1.0.0']) + + const tempDir = path.join(storeDir, 'v3/tmp') + const [patchDir] = fs.readdirSync(tempDir) + const userPatchDir = path.join(tempDir, patchDir, 'user') + fs.appendFileSync(path.join(userPatchDir, 'index.js'), '// test patching', 'utf8') + + await patchCommit.handler({ + dir: process.cwd(), + }, [userPatchDir]) + + const { manifest } = await readProjectManifest(process.cwd()) + expect(manifest.pnpm?.patchedDependencies).toStrictEqual({ + 'is-positive@1.0.0': 'patches/is-positive@1.0.0.patch', + }) + const patchContent = fs.readFileSync('patches/is-positive@1.0.0.patch', 'utf8') + expect(patchContent).toContain('diff --git') + expect(patchContent).toContain('// test patching') +}) diff --git a/packages/plugin-commands-patching/tsconfig.json b/packages/plugin-commands-patching/tsconfig.json new file mode 100644 index 000000000..a50d5aad6 --- /dev/null +++ b/packages/plugin-commands-patching/tsconfig.json @@ -0,0 +1,40 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts", + "../../typings/**/*.d.ts" + ], + "references": [ + { + "path": "../../privatePackages/prepare" + }, + { + "path": "../cli-utils" + }, + { + "path": "../config" + }, + { + "path": "../parse-wanted-dependency" + }, + { + "path": "../pick-registry-for-package" + }, + { + "path": "../read-package-json" + }, + { + "path": "../read-project-manifest" + }, + { + "path": "../store-connection-manager" + }, + { + "path": "../store-path" + } + ] +} diff --git a/packages/plugin-commands-patching/tsconfig.lint.json b/packages/plugin-commands-patching/tsconfig.lint.json new file mode 100644 index 000000000..0dc5add6b --- /dev/null +++ b/packages/plugin-commands-patching/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "../../typings/**/*.d.ts" + ] +} diff --git a/packages/plugin-commands-publishing/package.json b/packages/plugin-commands-publishing/package.json index d2a91a351..ed564d9ff 100644 --- a/packages/plugin-commands-publishing/package.json +++ b/packages/plugin-commands-publishing/package.json @@ -17,7 +17,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7776 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7777 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-rebuild/package.json b/packages/plugin-commands-rebuild/package.json index f90dfacb4..66cbce037 100644 --- a/packages/plugin-commands-rebuild/package.json +++ b/packages/plugin-commands-rebuild/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7777 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7778 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/plugin-commands-script-runners/package.json b/packages/plugin-commands-script-runners/package.json index a935ec39e..e7b5735be 100644 --- a/packages/plugin-commands-script-runners/package.json +++ b/packages/plugin-commands-script-runners/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7778 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7779 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "start": "tsc --watch", diff --git a/packages/plugin-commands-store/package.json b/packages/plugin-commands-store/package.json index 95d48655c..4d33c3853 100644 --- a/packages/plugin-commands-store/package.json +++ b/packages/plugin-commands-store/package.json @@ -16,7 +16,7 @@ "registry-mock": "registry-mock", "test:jest": "jest", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7779 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7780 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm run compile", "compile": "tsc --build && pnpm run lint --fix" diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index 9c5ad1eb8..ddd3ccdf8 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -46,6 +46,7 @@ "@pnpm/plugin-commands-installation": "workspace:10.2.0", "@pnpm/plugin-commands-listing": "workspace:5.0.13", "@pnpm/plugin-commands-outdated": "workspace:6.0.13", + "@pnpm/plugin-commands-patching": "workspace:0.0.0", "@pnpm/plugin-commands-publishing": "workspace:5.1.0", "@pnpm/plugin-commands-rebuild": "workspace:6.1.12", "@pnpm/plugin-commands-script-runners": "workspace:5.0.16", @@ -153,7 +154,7 @@ "test:jest": "jest", "pretest:e2e": "rimraf node_modules/.bin/pnpm", "test:e2e": "registry-mock prepare && run-p -r registry-mock test:jest", - "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7780 pnpm run test:e2e", + "_test": "cross-env PNPM_REGISTRY_MOCK_PORT=7781 pnpm run test:e2e", "test": "pnpm run compile && pnpm run _test", "prepublishOnly": "pnpm compile && npm cache clear --force && publish-packed --prune --npm-client=pnpm --dest=dist", "postpublish": "publish-packed", diff --git a/packages/pnpm/src/cmd/index.ts b/packages/pnpm/src/cmd/index.ts index ac31209a6..28f1b7084 100644 --- a/packages/pnpm/src/cmd/index.ts +++ b/packages/pnpm/src/cmd/index.ts @@ -6,6 +6,7 @@ import { add, fetch, install, link, prune, remove, unlink, update, importCommand import { list, ll, why } from '@pnpm/plugin-commands-listing' import { outdated } from '@pnpm/plugin-commands-outdated' import { pack, publish } from '@pnpm/plugin-commands-publishing' +import { patch, patchCommit } from '@pnpm/plugin-commands-patching' import { rebuild } from '@pnpm/plugin-commands-rebuild' import { create, @@ -108,6 +109,8 @@ const commands: CommandDefinition[] = [ ll, outdated, pack, + patch, + patchCommit, prune, publish, rebuild, diff --git a/packages/pnpm/tsconfig.json b/packages/pnpm/tsconfig.json index da152eddd..7fc17a16a 100644 --- a/packages/pnpm/tsconfig.json +++ b/packages/pnpm/tsconfig.json @@ -90,6 +90,9 @@ { "path": "../plugin-commands-outdated" }, + { + "path": "../plugin-commands-patching" + }, { "path": "../plugin-commands-publishing" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eb56af2f..0806e1ae8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1265,6 +1265,7 @@ importers: '@zkochan/rimraf': ^2.1.2 json-append: 1.1.1 load-json-file: ^6.2.0 + patch-package: ^6.4.7 path-exists: ^4.0.0 run-groups: ^3.0.1 dependencies: @@ -1274,6 +1275,7 @@ importers: '@pnpm/read-package-json': link:../read-package-json '@pnpm/store-controller-types': link:../store-controller-types '@pnpm/types': link:../types + patch-package: 6.4.7 path-exists: 4.0.0 run-groups: 3.0.1 devDependencies: @@ -2430,6 +2432,45 @@ importers: '@types/wrap-ansi': 3.0.0 '@types/zkochan__table': /@types/table/6.0.0 + packages/plugin-commands-patching: + specifiers: + '@pnpm/cli-utils': workspace:0.7.13 + '@pnpm/config': workspace:15.3.0 + '@pnpm/logger': ^4.0.0 + '@pnpm/parse-wanted-dependency': workspace:3.0.0 + '@pnpm/pick-registry-for-package': workspace:3.0.3 + '@pnpm/plugin-commands-patching': workspace:0.0.0 + '@pnpm/prepare': workspace:* + '@pnpm/read-package-json': workspace:6.0.4 + '@pnpm/read-project-manifest': workspace:3.0.4 + '@pnpm/registry-mock': 2.20.0 + '@pnpm/store-connection-manager': workspace:4.1.11 + '@pnpm/store-path': workspace:6.0.0 + '@types/ramda': 0.27.39 + escape-string-regexp: ^4.0.0 + ramda: ^0.27.1 + render-help: ^1.0.1 + safe-execa: ^0.1.1 + dependencies: + '@pnpm/cli-utils': link:../cli-utils + '@pnpm/config': link:../config + '@pnpm/parse-wanted-dependency': link:../parse-wanted-dependency + '@pnpm/pick-registry-for-package': link:../pick-registry-for-package + '@pnpm/read-package-json': link:../read-package-json + '@pnpm/read-project-manifest': link:../read-project-manifest + '@pnpm/store-connection-manager': link:../store-connection-manager + '@pnpm/store-path': link:../store-path + escape-string-regexp: 4.0.0 + ramda: 0.27.2 + render-help: 1.0.2 + safe-execa: 0.1.1 + devDependencies: + '@pnpm/logger': 4.0.0 + '@pnpm/plugin-commands-patching': 'link:' + '@pnpm/prepare': link:../../privatePackages/prepare + '@pnpm/registry-mock': 2.20.0 + '@types/ramda': 0.27.39 + packages/plugin-commands-publishing: specifiers: '@pnpm/cli-utils': workspace:0.7.13 @@ -2851,6 +2892,7 @@ importers: '@pnpm/plugin-commands-installation': workspace:10.2.0 '@pnpm/plugin-commands-listing': workspace:5.0.13 '@pnpm/plugin-commands-outdated': workspace:6.0.13 + '@pnpm/plugin-commands-patching': workspace:0.0.0 '@pnpm/plugin-commands-publishing': workspace:5.1.0 '@pnpm/plugin-commands-rebuild': workspace:6.1.12 '@pnpm/plugin-commands-script-runners': workspace:5.0.16 @@ -2939,6 +2981,7 @@ importers: '@pnpm/plugin-commands-installation': link:../plugin-commands-installation '@pnpm/plugin-commands-listing': link:../plugin-commands-listing '@pnpm/plugin-commands-outdated': link:../plugin-commands-outdated + '@pnpm/plugin-commands-patching': link:../plugin-commands-patching '@pnpm/plugin-commands-publishing': link:../plugin-commands-publishing '@pnpm/plugin-commands-rebuild': link:../plugin-commands-rebuild '@pnpm/plugin-commands-script-runners': link:../plugin-commands-script-runners @@ -7055,6 +7098,10 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + /ci-info/2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + dev: false + /ci-info/3.3.1: resolution: {integrity: sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==} @@ -7439,7 +7486,6 @@ packages: semver: 5.7.1 shebang-command: 1.2.0 which: 1.3.1 - dev: true /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -8752,6 +8798,12 @@ packages: locate-path: 6.0.0 path-exists: 4.0.0 + /find-yarn-workspace-root/2.0.0: + resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} + dependencies: + micromatch: 4.0.5 + dev: false + /find-yarn-workspace-root2/1.2.16: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: @@ -8860,7 +8912,6 @@ packages: graceful-fs: 4.2.10 jsonfile: 4.0.0 universalify: 0.1.2 - dev: true /fs-extra/8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} @@ -9580,6 +9631,13 @@ packages: resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} engines: {node: '>= 0.4'} + /is-ci/2.0.0: + resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} + hasBin: true + dependencies: + ci-info: 2.0.0 + dev: false + /is-ci/3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true @@ -9605,6 +9663,12 @@ packages: resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} dev: false + /is-docker/2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: false + /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -9792,6 +9856,13 @@ packages: resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==} dev: false + /is-wsl/2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: false + /is/3.3.0: resolution: {integrity: sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==} @@ -10391,7 +10462,6 @@ packages: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: graceful-fs: 4.2.10 - dev: true /jsonfile/6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -10462,6 +10532,12 @@ packages: engines: {node: '>=0.10.0'} dev: true + /klaw-sync/6.0.0: + resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} + dependencies: + graceful-fs: 4.2.10 + dev: false + /kleur/3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -11210,7 +11286,6 @@ packages: /nice-try/1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - dev: true /nise/5.1.1: resolution: {integrity: sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==} @@ -11512,6 +11587,14 @@ packages: dependencies: mimic-fn: 2.1.0 + /open/7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: false + /opt-cli/1.5.1: resolution: {integrity: sha1-BNtEexPJa5kusxaFJm9O0NlzbcI=} hasBin: true @@ -11775,6 +11858,26 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + /patch-package/6.4.7: + resolution: {integrity: sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==} + engines: {npm: '>5'} + hasBin: true + dependencies: + '@yarnpkg/lockfile': 1.1.0 + chalk: 2.4.2 + cross-spawn: 6.0.5 + find-yarn-workspace-root: 2.0.0 + fs-extra: 7.0.1 + is-ci: 2.0.0 + klaw-sync: 6.0.0 + minimist: 1.2.6 + open: 7.4.2 + rimraf: 3.0.2 + semver: 5.7.1 + slash: 2.0.0 + tmp: 0.0.33 + dev: false + /path-absolute/1.0.1: resolution: {integrity: sha512-gds5iRhSeOcDtj8gfWkRHLtZKTPsFVuh7utbjYtvnclw4XM+ffRzJrwqMhOD1PVqef7nBLmgsu1vIujjvAJrAw==} engines: {node: '>=4'} @@ -11794,7 +11897,6 @@ packages: /path-key/2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} - dev: true /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -12841,7 +12943,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: shebang-regex: 1.0.0 - dev: true /shebang-command/2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -12852,7 +12953,6 @@ packages: /shebang-regex/1.0.0: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} engines: {node: '>=0.10.0'} - dev: true /shebang-regex/3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} @@ -12924,6 +13024,11 @@ packages: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true + /slash/2.0.0: + resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} + engines: {node: '>=6'} + dev: false + /slash/3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -13504,7 +13609,6 @@ packages: engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 - dev: true /tmp/0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} @@ -13949,7 +14053,6 @@ packages: /universalify/0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - dev: true /universalify/2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} @@ -14353,7 +14456,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /which/2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} diff --git a/typings/local.d.ts b/typings/local.d.ts index 21dec3b6b..c87aed6ea 100644 --- a/typings/local.d.ts +++ b/typings/local.d.ts @@ -170,3 +170,7 @@ declare module 'mdast-util-to-string' { const anything: any; export = anything; } + +declare module 'patch-package/dist/applyPatches' { + export function applyPatch(opts: any): void; +}