Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/patched-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ Patches are filtered based on the target package's dependencies:
- Patches for packages not in the target's dependency tree are excluded

The patch files are copied to the isolated output, preserving their original
directory structure. The lockfile and `package.json` are updated with the correct
paths.
directory structure. pnpm 9 and 10 store the path in
`package.json`'s `pnpm.patchedDependencies`; when the source workspace
configuration declares the paths, its copied or filtered
`pnpm-workspace.yaml` does too. Their lockfile entries include both the path and
hash. pnpm 11 and later store the hash in the lockfile and the copied patch path
in `pnpm-workspace.yaml`. Bun stores patch paths in `package.json`.
143 changes: 143 additions & 0 deletions src/isolate.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import fs from "fs-extra";
import { createHash } from "node:crypto";
import os from "node:os";
import path from "node:path";
import { parse } from "yaml";
import { afterEach, describe, expect, it } from "vitest";
import { isolate } from "./isolate";

describe("isolate integration", () => {
const temporaryDirectories: string[] = [];
const leftPadPatchContents = "diff --git a/index.js b/index.js\n";
const leftPadPatchHash =
"2692094a267de7e28825147fd6cb2ebde098a4e68c25dfa3976ac806f4a1a784";

afterEach(async () => {
await Promise.all(
Expand Down Expand Up @@ -65,4 +70,142 @@ describe("isolate integration", () => {
fs.readJson(path.join(result, "package.json")),
).resolves.toMatchObject({ name: "functions-node", version: "1.0.0" });
});

it.each([10, 11])(
"writes pnpm %i patch settings to the lockfile and workspace configuration",
async (majorVersion) => {
Comment thread
0x80 marked this conversation as resolved.
const workspaceRoot = await fs.mkdtemp(
path.join(os.tmpdir(), "isolate-pnpm-patches-test-"),
);
temporaryDirectories.push(workspaceRoot);
const targetPackageDir = path.join(
workspaceRoot,
"packages",
"functions",
);
await fs.ensureDir(targetPackageDir);
await fs.ensureDir(path.join(workspaceRoot, "patches"));
await fs.writeJson(path.join(workspaceRoot, "package.json"), {
name: "workspace",
version: "1.0.0",
private: true,
packageManager: `pnpm@${majorVersion}.0.0`,
});
await fs.writeFile(
path.join(workspaceRoot, "pnpm-workspace.yaml"),
[
"packages:",
" - packages/*",
"patchedDependencies:",
" left-pad@1.3.0: patches/left-pad@1.3.0.patch",
"",
].join("\n"),
);
await fs.writeFile(
path.join(workspaceRoot, "pnpm-lock.yaml"),
[
"lockfileVersion: '9.0'",
"",
"settings:",
" autoInstallPeers: false",
"",
"importers:",
"",
" packages/functions:",
" dependencies:",
" left-pad:",
" specifier: 1.3.0",
" version: 1.3.0",
"",
"packages:",
"",
" left-pad@1.3.0:",
" resolution: {integrity: sha512-1}",
"",
"snapshots:",
"",
" left-pad@1.3.0: {}",
"",
"patchedDependencies:",
...(majorVersion >= 11
? [` left-pad@1.3.0: ${leftPadPatchHash}`]
: [
" left-pad@1.3.0:",
" path: patches/left-pad@1.3.0.patch",
` hash: ${leftPadPatchHash}`,
]),
"",
].join("\n"),
);
await fs.writeFile(
path.join(workspaceRoot, "patches", "left-pad@1.3.0.patch"),
leftPadPatchContents,
);
await fs.writeJson(path.join(targetPackageDir, "package.json"), {
name: "functions",
version: "1.0.0",
main: "./index.js",
files: ["index.js"],
dependencies: { "left-pad": "1.3.0" },
pnpm: {
patchedDependencies: {
"left-pad@1.3.0": "patches/stale-left-pad.patch",
},
},
});
await fs.writeFile(
path.join(targetPackageDir, "index.js"),
"export {};\n",
);

expect(
createHash("sha256").update(leftPadPatchContents).digest("hex"),
).toBe(leftPadPatchHash);

const isolateDir = await isolate({
targetPackagePath: targetPackageDir,
buildDirName: ".",
workspaceRoot: "../..",
});

await expect(
fs.readFile(
path.join(isolateDir, "patches", "left-pad@1.3.0.patch"),
"utf8",
),
).resolves.toBe(leftPadPatchContents);
await expect(
fs.pathExists(path.join(isolateDir, "patches", "stale-left-pad.patch")),
).resolves.toBe(false);

const manifest = await fs.readJson(path.join(isolateDir, "package.json"));
const workspaceSettings = parse(
await fs.readFile(path.join(isolateDir, "pnpm-workspace.yaml"), "utf8"),
);
const lockfile = parse(
await fs.readFile(path.join(isolateDir, "pnpm-lock.yaml"), "utf8"),
);

if (majorVersion >= 11) {
expect(manifest.pnpm).toBeUndefined();
} else {
expect(manifest.pnpm?.patchedDependencies).toEqual({
"left-pad@1.3.0": "patches/left-pad@1.3.0.patch",
});
}
expect(workspaceSettings.patchedDependencies).toEqual({
"left-pad@1.3.0": "patches/left-pad@1.3.0.patch",
});
expect(lockfile.patchedDependencies).toEqual(
majorVersion >= 11
? { "left-pad@1.3.0": leftPadPatchHash }
: {
"left-pad@1.3.0": {
path: "patches/left-pad@1.3.0.patch",
hash: leftPadPatchHash,
},
},
);
},
);
});
87 changes: 61 additions & 26 deletions src/isolate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ import {
import { detectPackageManager, shouldUsePnpmPack } from "./lib/package-manager";
import { getVersion } from "./lib/package-manager/helpers/infer-from-files";
import { copyPatches } from "./lib/patches/copy-patches";
import { writeIsolatePnpmWorkspace } from "./lib/patches/write-isolate-pnpm-workspace";
import {
getPnpmPatchedDependenciesOutput,
usesPnpmWorkspacePatchedDependencies,
} from "./lib/patches/pnpm-patched-dependencies";
import {
writeGeneratedIsolatePnpmWorkspace,
writeIsolatePnpmWorkspace,
} from "./lib/patches/write-isolate-pnpm-workspace";
import { createPackagesRegistry, listInternalPackages } from "./lib/registry";
import type { PackageManifest } from "./lib/types";
import {
Expand All @@ -37,7 +44,6 @@ import {
isRushWorkspace,
readTypedJson,
resetIsolateDir,
writeTypedYamlSync,
} from "./lib/utils";

const __dirname = getDirname(import.meta.url);
Expand Down Expand Up @@ -330,34 +336,53 @@ export function createIsolator(initialConfig?: IsolateConfig) {
});

const hasCopiedPatches = Object.keys(copiedPatches).length > 0;

/** Update manifest if patches were copied or npm fallback is needed */
if (hasCopiedPatches || usedFallbackToNpm) {
const pnpmPatchOutput = getPnpmPatchedDependenciesOutput({
majorVersion: packageManager.majorVersion,
copiedPatches,
});
const shouldRemoveLegacyPnpmPatchedDependencies =
packageManager.name === "pnpm" &&
!config.forceNpm &&
usesPnpmWorkspacePatchedDependencies(packageManager.majorVersion);

/** Update patch metadata for copied patches, pnpm 11 workspace output, or an npm fallback. */
if (
hasCopiedPatches ||
usedFallbackToNpm ||
shouldRemoveLegacyPnpmPatchedDependencies
) {
const manifest = await readManifest(isolateDir);

if (hasCopiedPatches) {
/**
* Extract just the paths for the manifest (lockfile needs full
* PatchFile). PNPM stores patches under pnpm.patchedDependencies, Bun
* at the top level.
*/
const patchEntries = Object.fromEntries(
Object.entries(copiedPatches).map(([spec, patchFile]) => [
spec,
patchFile.path,
]),
);
if (shouldRemoveLegacyPnpmPatchedDependencies) {
delete manifest.pnpm?.patchedDependencies;

if (packageManager.name === "bun") {
manifest.patchedDependencies = patchEntries;
} else {
manifest.pnpm ??= {};
manifest.pnpm.patchedDependencies = patchEntries;
if (manifest.pnpm && Object.keys(manifest.pnpm).length === 0) {
delete manifest.pnpm;
}
}

log.debug(
`Added ${Object.keys(copiedPatches).length} patches to isolated package.json`,
);
if (hasCopiedPatches) {
if (packageManager.name === "bun") {
manifest.patchedDependencies = Object.fromEntries(
Object.entries(copiedPatches).map(([spec, patchFile]) => [
spec,
patchFile.path,
]),
);
log.debug(
`Added ${Object.keys(copiedPatches).length} patches to isolated package.json`,
);
} else if (packageManager.name === "pnpm") {
const { manifest: pnpmPatchPaths } = pnpmPatchOutput;

if (pnpmPatchPaths) {
Comment thread
0x80 marked this conversation as resolved.
manifest.pnpm ??= {};
manifest.pnpm.patchedDependencies = pnpmPatchPaths;
log.debug(
`Added ${Object.keys(copiedPatches).length} patches to isolated package.json`,
);
}
}
}

if (usedFallbackToNpm) {
Expand Down Expand Up @@ -393,16 +418,26 @@ export function createIsolator(initialConfig?: IsolateConfig) {

const packages = packagesFolderNames.map((x) => path.join(x, "/*"));

writeTypedYamlSync(path.join(isolateDir, "pnpm-workspace.yaml"), {
writeGeneratedIsolatePnpmWorkspace({
isolateDir,
packages,
majorVersion: packageManager.majorVersion,
copiedPatches,
});
} else {
writeIsolatePnpmWorkspace({
workspaceRootDir,
isolateDir,
majorVersion: packageManager.majorVersion,
copiedPatches,
});
}

if (pnpmPatchOutput.workspace) {
log.debug(
`Added ${Object.keys(copiedPatches).length} patches to isolated pnpm-workspace.yaml`,
);
}
}

if (packageManager.name === "bun" && !config.forceNpm) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "patch-validation-workspace",
"private": true,
"packageManager": "pnpm@10.0.0",
"pnpm": {
"patchedDependencies": {
"left-pad@1.3.0": "patches/left-pad@1.3.0.patch"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "functions",
"version": "1.0.0",
"files": [
"index.js"
],
"dependencies": {
"left-pad": "1.3.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/index.js b/index.js
index e90aec35d979c42dcd4ddfacb4768c00d7102349..ba97d1bf59b5cf6de4ddbdcd49a7004f0c7715e5 100644
--- a/index.js
+++ b/index.js
@@ -4,7 +4,7 @@
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
'use strict';
-module.exports = leftPad;
+module.exports = (str, len, ch) => leftPad(str, len, ch);

var cache = [
'',

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
packages:
- packages/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "patch-validation-workspace",
"private": true,
"packageManager": "pnpm@12.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "functions",
"version": "1.0.0",
"files": [
"index.js"
],
"dependencies": {
"left-pad": "1.3.0"
}
}
Loading
Loading