Skip to content
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"yaml": "^2.8.1"
},
"devDependencies": {
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
"@types/fs-extra": "^11.0.4",
"@types/node": "^24.4.0",
"@types/npmcli__config": "^6.0.3",
Expand Down
40 changes: 23 additions & 17 deletions pnpm-lock.yaml

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

23 changes: 23 additions & 0 deletions src/isolate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from "./lib/output";
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 { createPackagesRegistry, listInternalPackages } from "./lib/registry";
import type { PackageManifest } from "./lib/types";
import {
Expand Down Expand Up @@ -210,6 +211,28 @@ export function createIsolator(config?: IsolateConfig) {
config,
});

/** Copy patch files if includePatchedDependencies is enabled */
const copiedPatches = await copyPatches({
workspaceRootDir,
targetPackageManifest,
isolateDir,
includePatchedDependencies: config.includePatchedDependencies,
includeDevDependencies: config.includeDevDependencies,
});

/** Add copied patches to the isolated package.json */
if (Object.keys(copiedPatches).length > 0) {
const manifest = await readManifest(isolateDir);
if (!manifest.pnpm) {
manifest.pnpm = {};
}
manifest.pnpm.patchedDependencies = copiedPatches;
await writeManifest(isolateDir, manifest);
log.debug(
`Added ${Object.keys(copiedPatches).length} patches to isolated package.json`
);
}

if (usedFallbackToNpm) {
/**
* When we fall back to NPM, we set the manifest package manager to the
Expand Down
101 changes: 93 additions & 8 deletions src/lib/lockfile/helpers/generate-pnpm-lockfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,83 @@ import {
import { pruneLockfile as pruneLockfile_v8 } from "pnpm_prune_lockfile_v8";
import { pruneLockfile as pruneLockfile_v9 } from "pnpm_prune_lockfile_v9";
import { pick } from "remeda";
import type { Logger } from "~/lib/logger";
import { useLogger } from "~/lib/logger";
import type { PackageManifest, PackagesRegistry } from "~/lib/types";
import { getErrorMessage, isRushWorkspace } from "~/lib/utils";
import { pnpmMapImporter } from "./pnpm-map-importer";

function filterPatchedDependencies(
originalPatchedDependencies: any,
targetPackageManifest: PackageManifest,
includeDevDependencies: boolean,
log: Logger
): any {
if (
!originalPatchedDependencies ||
typeof originalPatchedDependencies !== "object"
) {
return undefined;
}

const getPackageName = (packageSpec: string): string => {
// Handle scoped packages: @scope/package@version -> @scope/package
if (packageSpec.startsWith("@")) {
const parts = packageSpec.split("@");
return `@${parts[1]}`;
}
// Handle regular packages: package@version -> package
return packageSpec.split("@")[0];
};

const filteredPatches: any = {};
let includedCount = 0;
let excludedCount = 0;

for (const [packageSpec, patchInfo] of Object.entries(
originalPatchedDependencies
)) {
const packageName = getPackageName(packageSpec);

// Check if it's a regular dependency
if (targetPackageManifest.dependencies?.[packageName]) {
filteredPatches[packageSpec] = patchInfo;
includedCount++;
log.debug(
`Including production dependency patch in lockfile: ${packageSpec}`
);
continue;
}

// Check if it's a dev dependency and we should include dev dependencies
if (targetPackageManifest.devDependencies?.[packageName]) {
if (includeDevDependencies) {
filteredPatches[packageSpec] = patchInfo;
includedCount++;
log.debug(`Including dev dependency patch in lockfile: ${packageSpec}`);
} else {
excludedCount++;
log.debug(
`Excluding dev dependency patch from lockfile: ${packageSpec}`
);
}
continue;
}

// Package not found in dependencies or devDependencies
log.debug(
`Excluding patch from lockfile: ${packageSpec} (package "${packageName}" not found in target dependencies)`
);
excludedCount++;
}

log.debug(
`Filtered patched dependencies: ${includedCount} included, ${excludedCount} excluded`
);

return Object.keys(filteredPatches).length > 0 ? filteredPatches : undefined;
}

export async function generatePnpmLockfile({
workspaceRootDir,
targetPackageDir,
Expand All @@ -40,14 +112,22 @@ export async function generatePnpmLockfile({
includePatchedDependencies: boolean;
}) {
/**
* For now we will assume that the lockfile format might not change in the
* versions after 9, because we might get lucky. If it does change, things
* would break either way.
* PNPM 10+ uses the same lockfile format as version 9, but with
* lockfileVersion: '10.0' Since @pnpm/lockfile-file v10 packages don't exist
* yet, we use v9 packages for PNPM 10+. This should work because PNPM
* maintains backward compatibility, but we log a warning to alert users of
* potential edge cases.
*/
const useVersion9 = majorVersion >= 9;

const log = useLogger();

if (majorVersion >= 10) {
log.debug(
`Using PNPM v${majorVersion} with v9 lockfile packages - this should work but may have limitations`
);
}

log.debug("Generating PNPM lockfile...");

try {
Expand Down Expand Up @@ -163,13 +243,18 @@ export async function generatePnpmLockfile({
}

/**
* Don't know how to map the patched dependencies yet, so we just include
* them but I don't think it would work like this. The important thing for
* now is that they are omitted by default, because that is the most common
* use case.
* Filter patched dependencies to only include patches for packages that
* will actually be present in the isolated lockfile based on dependency
* type. We read patchedDependencies from workspace root, but filter based
* on target package dependencies.
*/
const patchedDependencies = includePatchedDependencies
? lockfile.patchedDependencies
? filterPatchedDependencies(
lockfile.patchedDependencies,
targetPackageManifest,
includeDevDependencies,
log
)
: undefined;

useVersion9
Expand Down
Loading