isolate-package 1.33.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/isolate-bin.mjs +5 -6
- package/dist/isolate-bin.mjs.map +1 -1
- package/dist/{isolate-DyRD5Zd_.mjs → isolate-ts-Igq7C.mjs} +888 -271
- package/dist/isolate-ts-Igq7C.mjs.map +1 -0
- package/package.json +23 -19
- package/src/get-internal-package-names.test.ts +1 -1
- package/src/get-internal-package-names.ts +2 -2
- package/src/isolate-bin.ts +5 -5
- package/src/isolate.ts +22 -17
- package/src/lib/config.test.ts +1 -1
- package/src/lib/config.ts +3 -3
- package/src/lib/lockfile/helpers/bun-lockfile.ts +153 -0
- package/src/lib/lockfile/helpers/generate-bun-lockfile.test.ts +3 -3
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +14 -146
- package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +1 -5
- package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +311 -16
- package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +193 -22
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.test.ts +83 -2
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.ts +33 -6
- package/src/lib/lockfile/helpers/generate-yarn-lockfile.ts +5 -5
- package/src/lib/lockfile/process-lockfile.test.ts +2 -2
- package/src/lib/manifest/adapt-target-package-manifest.ts +22 -13
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +72 -3
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +22 -12
- package/src/lib/manifest/helpers/adapt-manifest-internal-deps.ts +1 -1
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +4 -4
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +7 -7
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.test.ts +410 -0
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.ts +115 -27
- package/src/lib/manifest/io.ts +6 -2
- package/src/lib/manifest/validate-manifest.ts +2 -2
- package/src/lib/output/get-build-output-dir.ts +1 -1
- package/src/lib/output/pack-dependencies.ts +1 -1
- package/src/lib/output/process-build-output-files.ts +6 -17
- package/src/lib/package-manager/helpers/infer-from-files.ts +5 -5
- package/src/lib/package-manager/helpers/infer-from-manifest.ts +7 -8
- package/src/lib/package-manager/index.ts +1 -1
- package/src/lib/package-manager/names.ts +8 -10
- package/src/lib/patches/collect-installed-names-bun.test.ts +154 -0
- package/src/lib/patches/collect-installed-names-bun.ts +87 -0
- package/src/lib/patches/collect-installed-names-pnpm.test.ts +316 -0
- package/src/lib/patches/collect-installed-names-pnpm.ts +365 -0
- package/src/lib/patches/copy-patches.test.ts +130 -13
- package/src/lib/patches/copy-patches.ts +47 -10
- package/src/lib/patches/write-isolate-pnpm-workspace.test.ts +83 -3
- package/src/lib/patches/write-isolate-pnpm-workspace.ts +4 -4
- package/src/lib/registry/collect-reachable-package-names.test.ts +1 -1
- package/src/lib/registry/create-packages-registry.ts +34 -31
- package/src/lib/registry/helpers/find-packages-globs.ts +23 -19
- package/src/lib/registry/list-internal-packages.test.ts +2 -2
- package/src/lib/types.ts +2 -2
- package/src/lib/utils/filter-patched-dependencies.test.ts +1 -1
- package/src/lib/utils/filter-patched-dependencies.ts +2 -2
- package/src/lib/utils/get-dirname.ts +1 -1
- package/src/lib/utils/index.ts +1 -1
- package/src/lib/utils/json.ts +12 -14
- package/src/lib/utils/pack.ts +32 -22
- package/src/lib/utils/reset-isolate-dir.test.ts +165 -0
- package/src/lib/utils/reset-isolate-dir.ts +147 -0
- package/src/lib/utils/unpack.test.ts +76 -0
- package/src/lib/utils/unpack.ts +16 -10
- package/src/lib/utils/wait-for-complete-file.test.ts +105 -0
- package/src/lib/utils/wait-for-complete-file.ts +44 -0
- package/src/lib/utils/yaml.ts +8 -9
- package/src/testing/setup.ts +1 -1
- package/dist/isolate-DyRD5Zd_.mjs.map +0 -1
|
@@ -2,39 +2,45 @@ import fs from "fs-extra";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readWantedLockfile as readWantedLockfile_v8 } from "pnpm_lockfile_file_v8";
|
|
4
4
|
import { readWantedLockfile as readWantedLockfile_v9 } from "pnpm_lockfile_file_v9";
|
|
5
|
-
import { useLogger } from "
|
|
6
|
-
import { usePackageManager } from "
|
|
7
|
-
import { collectReachablePackageNames } from "
|
|
5
|
+
import { useLogger } from "#/lib/logger";
|
|
6
|
+
import { usePackageManager } from "#/lib/package-manager";
|
|
7
|
+
import { collectReachablePackageNames } from "#/lib/registry";
|
|
8
8
|
import type {
|
|
9
9
|
PackageManifest,
|
|
10
10
|
PackagesRegistry,
|
|
11
11
|
PatchFile,
|
|
12
12
|
PnpmSettings,
|
|
13
|
-
} from "
|
|
13
|
+
} from "#/lib/types";
|
|
14
14
|
import {
|
|
15
15
|
filterPatchedDependencies,
|
|
16
16
|
getRootRelativeLogPath,
|
|
17
17
|
isRushWorkspace,
|
|
18
18
|
readTypedJson,
|
|
19
19
|
readTypedYamlSync,
|
|
20
|
-
} from "
|
|
20
|
+
} from "#/lib/utils";
|
|
21
|
+
import { collectInstalledNamesFromBunLockfile } from "./collect-installed-names-bun";
|
|
22
|
+
import { collectInstalledNamesFromPnpmLockfile } from "./collect-installed-names-pnpm";
|
|
21
23
|
|
|
22
24
|
export async function copyPatches({
|
|
23
25
|
workspaceRootDir,
|
|
26
|
+
targetPackageDir,
|
|
24
27
|
targetPackageManifest,
|
|
25
28
|
packagesRegistry,
|
|
29
|
+
internalDepPackageNames,
|
|
26
30
|
isolateDir,
|
|
27
31
|
includeDevDependencies,
|
|
28
32
|
}: {
|
|
29
33
|
workspaceRootDir: string;
|
|
34
|
+
targetPackageDir: string;
|
|
30
35
|
targetPackageManifest: PackageManifest;
|
|
31
36
|
packagesRegistry: PackagesRegistry;
|
|
37
|
+
internalDepPackageNames: string[];
|
|
32
38
|
isolateDir: string;
|
|
33
39
|
includeDevDependencies: boolean;
|
|
34
40
|
}): Promise<Record<string, PatchFile>> {
|
|
35
41
|
const log = useLogger();
|
|
36
42
|
|
|
37
|
-
const { name: packageManagerName } = usePackageManager();
|
|
43
|
+
const { name: packageManagerName, majorVersion } = usePackageManager();
|
|
38
44
|
|
|
39
45
|
let patchedDependencies: Record<string, string> | undefined;
|
|
40
46
|
|
|
@@ -44,9 +50,9 @@ export async function copyPatches({
|
|
|
44
50
|
*/
|
|
45
51
|
if (packageManagerName === "pnpm") {
|
|
46
52
|
try {
|
|
47
|
-
const pnpmSettings = readTypedYamlSync
|
|
53
|
+
const pnpmSettings = readTypedYamlSync(
|
|
48
54
|
path.join(workspaceRootDir, "pnpm-workspace.yaml"),
|
|
49
|
-
);
|
|
55
|
+
) as PnpmSettings | undefined;
|
|
50
56
|
patchedDependencies = pnpmSettings?.patchedDependencies;
|
|
51
57
|
} catch (error) {
|
|
52
58
|
log.warn(
|
|
@@ -67,9 +73,9 @@ export async function copyPatches({
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
try {
|
|
70
|
-
const workspaceRootManifest = await readTypedJson
|
|
76
|
+
const workspaceRootManifest = (await readTypedJson(
|
|
71
77
|
path.join(workspaceRootDir, "package.json"),
|
|
72
|
-
);
|
|
78
|
+
)) as PackageManifest;
|
|
73
79
|
/** PNPM stores patches under pnpm.patchedDependencies, Bun at the top level */
|
|
74
80
|
patchedDependencies =
|
|
75
81
|
workspaceRootManifest?.pnpm?.patchedDependencies ??
|
|
@@ -102,6 +108,37 @@ export async function copyPatches({
|
|
|
102
108
|
includeDevDependencies,
|
|
103
109
|
});
|
|
104
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Manifest-based reachability misses external→external transitives because
|
|
113
|
+
* external manifests aren't loaded here. Walk the package-manager's
|
|
114
|
+
* lockfile to also pick up those names, so a patch for a deeply-nested
|
|
115
|
+
* external dep (e.g. `@react-pdf/render` reached via `@react-pdf/renderer`)
|
|
116
|
+
* survives isolation.
|
|
117
|
+
*/
|
|
118
|
+
const lockfileInstalledNames =
|
|
119
|
+
packageManagerName === "pnpm"
|
|
120
|
+
? await collectInstalledNamesFromPnpmLockfile({
|
|
121
|
+
workspaceRootDir,
|
|
122
|
+
targetPackageDir,
|
|
123
|
+
internalDepPackageNames,
|
|
124
|
+
packagesRegistry,
|
|
125
|
+
majorVersion,
|
|
126
|
+
includeDevDependencies,
|
|
127
|
+
})
|
|
128
|
+
: packageManagerName === "bun"
|
|
129
|
+
? collectInstalledNamesFromBunLockfile({
|
|
130
|
+
workspaceRootDir,
|
|
131
|
+
targetPackageDir,
|
|
132
|
+
internalDepPackageNames,
|
|
133
|
+
packagesRegistry,
|
|
134
|
+
includeDevDependencies,
|
|
135
|
+
})
|
|
136
|
+
: new Set<string>();
|
|
137
|
+
|
|
138
|
+
for (const name of lockfileInstalledNames) {
|
|
139
|
+
reachableDependencyNames.add(name);
|
|
140
|
+
}
|
|
141
|
+
|
|
105
142
|
const filteredPatches = filterPatchedDependencies({
|
|
106
143
|
patchedDependencies,
|
|
107
144
|
targetPackageManifest,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { PatchFile } from "
|
|
2
|
+
import type { PatchFile } from "#/lib/types";
|
|
3
3
|
import { writeIsolatePnpmWorkspace } from "./write-isolate-pnpm-workspace";
|
|
4
4
|
|
|
5
5
|
vi.mock("fs-extra", () => ({
|
|
@@ -8,14 +8,14 @@ vi.mock("fs-extra", () => ({
|
|
|
8
8
|
},
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
|
-
vi.mock("
|
|
11
|
+
vi.mock("#/lib/utils", () => ({
|
|
12
12
|
readTypedYamlSync: vi.fn(),
|
|
13
13
|
writeTypedYamlSync: vi.fn(),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
const fs = vi.mocked((await import("fs-extra")).default);
|
|
17
17
|
const { readTypedYamlSync, writeTypedYamlSync } = vi.mocked(
|
|
18
|
-
await import("
|
|
18
|
+
await import("#/lib/utils"),
|
|
19
19
|
);
|
|
20
20
|
|
|
21
21
|
const workspaceRootDir = "/workspace";
|
|
@@ -139,6 +139,86 @@ describe("writeIsolatePnpmWorkspace", () => {
|
|
|
139
139
|
);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Regression test for issue #189: pnpm 11 expresses the build-script policy
|
|
144
|
+
* via `allowBuilds` in pnpm-workspace.yaml (and removes the older
|
|
145
|
+
* `pnpm.onlyBuiltDependencies` / `ignoredBuiltDependencies` fields from
|
|
146
|
+
* package.json). The verbatim copy must carry that field — along with other
|
|
147
|
+
* workspace-level settings like `minimumReleaseAge` — into the isolate
|
|
148
|
+
* output so downstream `pnpm install` honors the same policy.
|
|
149
|
+
*/
|
|
150
|
+
it("preserves pnpm 11 workspace settings (allowBuilds, minimumReleaseAge) when no patches are involved", () => {
|
|
151
|
+
readTypedYamlSync.mockReturnValue({
|
|
152
|
+
packages: ["apps/*", "packages/*"],
|
|
153
|
+
allowBuilds: {
|
|
154
|
+
puppeteer: true,
|
|
155
|
+
esbuild: true,
|
|
156
|
+
},
|
|
157
|
+
minimumReleaseAge: 10_080,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
writeIsolatePnpmWorkspace({
|
|
161
|
+
workspaceRootDir,
|
|
162
|
+
isolateDir,
|
|
163
|
+
copiedPatches: {},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* With no patchedDependencies in the source yaml, the file is copied
|
|
168
|
+
* verbatim — preserving `allowBuilds` and any other top-level settings.
|
|
169
|
+
*/
|
|
170
|
+
expect(writeTypedYamlSync).not.toHaveBeenCalled();
|
|
171
|
+
expect(fs.copyFileSync).toHaveBeenCalledWith(
|
|
172
|
+
"/workspace/pnpm-workspace.yaml",
|
|
173
|
+
"/workspace/isolate/pnpm-workspace.yaml",
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* When patches are being filtered, the rewrite path must still carry
|
|
179
|
+
* `allowBuilds` into the output yaml — otherwise pnpm 11's build-script
|
|
180
|
+
* policy is silently dropped.
|
|
181
|
+
*/
|
|
182
|
+
it("preserves allowBuilds when rewriting to filter patchedDependencies", () => {
|
|
183
|
+
readTypedYamlSync.mockReturnValue({
|
|
184
|
+
packages: ["apps/*", "packages/*"],
|
|
185
|
+
allowBuilds: {
|
|
186
|
+
puppeteer: true,
|
|
187
|
+
},
|
|
188
|
+
patchedDependencies: {
|
|
189
|
+
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
|
|
190
|
+
"axios@1.6.0": "patches/axios@1.6.0.patch",
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const copiedPatches: Record<string, PatchFile> = {
|
|
195
|
+
"lodash@4.17.21": {
|
|
196
|
+
path: "patches/lodash@4.17.21.patch",
|
|
197
|
+
hash: "abc",
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
writeIsolatePnpmWorkspace({
|
|
202
|
+
workspaceRootDir,
|
|
203
|
+
isolateDir,
|
|
204
|
+
copiedPatches,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(fs.copyFileSync).not.toHaveBeenCalled();
|
|
208
|
+
expect(writeTypedYamlSync).toHaveBeenCalledWith(
|
|
209
|
+
"/workspace/isolate/pnpm-workspace.yaml",
|
|
210
|
+
{
|
|
211
|
+
packages: ["apps/*", "packages/*"],
|
|
212
|
+
allowBuilds: {
|
|
213
|
+
puppeteer: true,
|
|
214
|
+
},
|
|
215
|
+
patchedDependencies: {
|
|
216
|
+
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
142
222
|
it("copies verbatim when every patch is kept (preserving comments and order)", () => {
|
|
143
223
|
readTypedYamlSync.mockReturnValue({
|
|
144
224
|
packages: ["packages/*"],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { useLogger } from "
|
|
4
|
-
import type { PatchFile, PnpmSettings } from "
|
|
5
|
-
import { readTypedYamlSync, writeTypedYamlSync } from "
|
|
3
|
+
import { useLogger } from "#/lib/logger";
|
|
4
|
+
import type { PatchFile, PnpmSettings } from "#/lib/types";
|
|
5
|
+
import { readTypedYamlSync, writeTypedYamlSync } from "#/lib/utils";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Copy `pnpm-workspace.yaml` from the workspace root to the isolate directory,
|
|
@@ -39,7 +39,7 @@ export function writeIsolatePnpmWorkspace({
|
|
|
39
39
|
let settings: PnpmSettings | undefined;
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
|
-
settings = readTypedYamlSync
|
|
42
|
+
settings = readTypedYamlSync(sourcePath) as PnpmSettings | undefined;
|
|
43
43
|
} catch (error) {
|
|
44
44
|
log.warn(
|
|
45
45
|
`Could not read pnpm-workspace.yaml, falling back to verbatim copy: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { PackageManifest, PackagesRegistry } from "
|
|
2
|
+
import type { PackageManifest, PackagesRegistry } from "#/lib/types";
|
|
3
3
|
import { collectReachablePackageNames } from "./collect-reachable-package-names";
|
|
4
4
|
|
|
5
5
|
function entry(manifest: PackageManifest) {
|
|
@@ -28,38 +28,41 @@ export async function createPackagesRegistry(
|
|
|
28
28
|
workspaceRootDir,
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const manifestPath = path.join(absoluteDir, "package.json");
|
|
31
|
+
const entries = await Promise.all(
|
|
32
|
+
allPackages.map(async (rootRelativeDir) => {
|
|
33
|
+
const absoluteDir = path.join(workspaceRootDir, rootRelativeDir);
|
|
34
|
+
const manifestPath = path.join(absoluteDir, "package.json");
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
log.debug(`Registering package ${rootRelativeDir}`);
|
|
36
|
+
if (!fs.existsSync(manifestPath)) {
|
|
37
|
+
log.warn(
|
|
38
|
+
`Ignoring directory ${rootRelativeDir} because it does not contain a package.json file`,
|
|
39
|
+
);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
path.join(absoluteDir, "package.json"),
|
|
47
|
-
);
|
|
43
|
+
log.debug(`Registering package ${rootRelativeDir}`);
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
const manifest = (await readTypedJson(
|
|
46
|
+
path.join(absoluteDir, "package.json"),
|
|
47
|
+
)) as PackageManifest;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
manifest,
|
|
51
|
+
rootRelativeDir,
|
|
52
|
+
absoluteDir,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const registry: PackagesRegistry = entries.reduce<PackagesRegistry>(
|
|
58
|
+
(acc, info) => {
|
|
59
|
+
if (info) {
|
|
60
|
+
acc[info.manifest.name] = info;
|
|
61
|
+
}
|
|
62
|
+
return acc;
|
|
63
|
+
},
|
|
64
|
+
{},
|
|
65
|
+
);
|
|
63
66
|
|
|
64
67
|
return registry;
|
|
65
68
|
}
|
|
@@ -73,9 +76,9 @@ function listWorkspacePackages(
|
|
|
73
76
|
workspaceRootDir: string,
|
|
74
77
|
) {
|
|
75
78
|
if (isRushWorkspace(workspaceRootDir)) {
|
|
76
|
-
const rushConfig = readTypedJsonSync
|
|
79
|
+
const rushConfig = readTypedJsonSync(
|
|
77
80
|
path.join(workspaceRootDir, "rush.json"),
|
|
78
|
-
);
|
|
81
|
+
) as RushConfig;
|
|
79
82
|
|
|
80
83
|
return rushConfig.projects.map(({ projectFolder }) => projectFolder);
|
|
81
84
|
} else {
|
|
@@ -13,16 +13,16 @@ import {
|
|
|
13
13
|
* monorepo. This configuration is dependent on the package manager used, and I
|
|
14
14
|
* don't know if we're covering all cases yet...
|
|
15
15
|
*/
|
|
16
|
-
export function findPackagesGlobs(workspaceRootDir: string) {
|
|
16
|
+
export function findPackagesGlobs(workspaceRootDir: string): string[] {
|
|
17
17
|
const log = useLogger();
|
|
18
18
|
|
|
19
19
|
const packageManager = usePackageManager();
|
|
20
20
|
|
|
21
21
|
switch (packageManager.name) {
|
|
22
22
|
case "pnpm": {
|
|
23
|
-
const workspaceConfig = readTypedYamlSync
|
|
23
|
+
const workspaceConfig = readTypedYamlSync(
|
|
24
24
|
path.join(workspaceRootDir, "pnpm-workspace.yaml"),
|
|
25
|
-
);
|
|
25
|
+
) as { packages: string[] } | undefined;
|
|
26
26
|
|
|
27
27
|
if (!workspaceConfig) {
|
|
28
28
|
throw new Error(
|
|
@@ -48,9 +48,9 @@ export function findPackagesGlobs(workspaceRootDir: string) {
|
|
|
48
48
|
"package.json",
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
const { workspaces } = readTypedJsonSync
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const { workspaces } = readTypedJsonSync(workspaceRootManifestPath) as {
|
|
52
|
+
workspaces: string[];
|
|
53
|
+
};
|
|
54
54
|
|
|
55
55
|
if (!workspaces) {
|
|
56
56
|
throw new Error(
|
|
@@ -60,21 +60,25 @@ export function findPackagesGlobs(workspaceRootDir: string) {
|
|
|
60
60
|
|
|
61
61
|
if (Array.isArray(workspaces)) {
|
|
62
62
|
return workspaces;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* For Yarn, workspaces could be defined as an object with { packages:
|
|
66
|
-
* [], nohoist: [] }. See
|
|
67
|
-
* https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
|
|
68
|
-
*/
|
|
69
|
-
const workspacesObject = workspaces as { packages?: string[] };
|
|
63
|
+
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
/**
|
|
66
|
+
* For Yarn, workspaces could be defined as an object with { packages: [],
|
|
67
|
+
* nohoist: [] }. See
|
|
68
|
+
* https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
|
|
69
|
+
*/
|
|
70
|
+
const workspacesObject = workspaces as { packages?: string[] };
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
assert(
|
|
73
|
+
Array.isArray(workspacesObject.packages),
|
|
74
|
+
"workspaces.packages must be an array",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return workspacesObject.packages;
|
|
78
78
|
}
|
|
79
|
+
default:
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Unsupported package manager: ${packageManager.name as string}`,
|
|
82
|
+
);
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { PackageManifest, PackagesRegistry } from "
|
|
2
|
+
import type { PackageManifest, PackagesRegistry } from "#/lib/types";
|
|
3
3
|
import { listInternalPackages } from "./list-internal-packages";
|
|
4
4
|
|
|
5
5
|
const mockWarn = vi.fn();
|
|
6
6
|
|
|
7
|
-
vi.mock("
|
|
7
|
+
vi.mock("#/lib/logger", () => ({
|
|
8
8
|
useLogger: () => ({
|
|
9
9
|
debug: vi.fn(),
|
|
10
10
|
info: vi.fn(),
|
package/src/lib/types.ts
CHANGED
|
@@ -6,10 +6,10 @@ export type { PnpmSettings } from "@pnpm/types";
|
|
|
6
6
|
* Represents a patch file entry in the pnpm lockfile. Contains the path to the
|
|
7
7
|
* patch file and its content hash.
|
|
8
8
|
*/
|
|
9
|
-
export
|
|
9
|
+
export type PatchFile = {
|
|
10
10
|
path: string;
|
|
11
11
|
hash: string;
|
|
12
|
-
}
|
|
12
|
+
};
|
|
13
13
|
|
|
14
14
|
export type PackageManifest = PnpmPackageManifest & {
|
|
15
15
|
packageManager?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { PackageManifest } from "
|
|
2
|
+
import type { PackageManifest } from "#/lib/types";
|
|
3
3
|
import { filterPatchedDependencies } from "./filter-patched-dependencies";
|
|
4
4
|
|
|
5
5
|
describe("filterPatchedDependencies", () => {
|
package/src/lib/utils/index.ts
CHANGED
package/src/lib/utils/json.ts
CHANGED
|
@@ -3,32 +3,30 @@ import stripJsonComments from "strip-json-comments";
|
|
|
3
3
|
import { getErrorMessage } from "./get-error-message";
|
|
4
4
|
|
|
5
5
|
/** @todo Pass in zod schema and validate */
|
|
6
|
-
export function readTypedJsonSync
|
|
6
|
+
export function readTypedJsonSync(filePath: string): unknown {
|
|
7
7
|
try {
|
|
8
8
|
const rawContent = fs.readFileSync(filePath, "utf-8");
|
|
9
|
-
|
|
9
|
+
return JSON.parse(
|
|
10
10
|
stripJsonComments(rawContent, { trailingCommas: true }),
|
|
11
|
-
) as
|
|
12
|
-
|
|
13
|
-
} catch (err) {
|
|
11
|
+
) as unknown;
|
|
12
|
+
} catch (error) {
|
|
14
13
|
throw new Error(
|
|
15
|
-
`Failed to read JSON from ${filePath}: ${getErrorMessage(
|
|
16
|
-
{ cause:
|
|
14
|
+
`Failed to read JSON from ${filePath}: ${getErrorMessage(error)}`,
|
|
15
|
+
{ cause: error },
|
|
17
16
|
);
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export async function readTypedJson
|
|
20
|
+
export async function readTypedJson(filePath: string): Promise<unknown> {
|
|
22
21
|
try {
|
|
23
22
|
const rawContent = await fs.readFile(filePath, "utf-8");
|
|
24
|
-
|
|
23
|
+
return JSON.parse(
|
|
25
24
|
stripJsonComments(rawContent, { trailingCommas: true }),
|
|
26
|
-
) as
|
|
27
|
-
|
|
28
|
-
} catch (err) {
|
|
25
|
+
) as unknown;
|
|
26
|
+
} catch (error) {
|
|
29
27
|
throw new Error(
|
|
30
|
-
`Failed to read JSON from ${filePath}: ${getErrorMessage(
|
|
31
|
-
{ cause:
|
|
28
|
+
`Failed to read JSON from ${filePath}: ${getErrorMessage(error)}`,
|
|
29
|
+
{ cause: error },
|
|
32
30
|
);
|
|
33
31
|
}
|
|
34
32
|
}
|
package/src/lib/utils/pack.ts
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { exec } from "node:child_process";
|
|
3
|
-
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { useLogger } from "../logger";
|
|
6
5
|
import { shouldUsePnpmPack } from "../package-manager";
|
|
7
6
|
import { getErrorMessage } from "./get-error-message";
|
|
7
|
+
import { waitForCompleteFile } from "./wait-for-complete-file";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* How long to wait for the packed tarball to appear and stop growing on disk
|
|
11
|
+
* after `pnpm pack` / `npm pack` has exited.
|
|
12
|
+
*/
|
|
13
|
+
const PACK_FILE_READY_TIMEOUT_MS = 5000;
|
|
14
|
+
const PACK_FILE_READY_POLL_MS = 50;
|
|
8
15
|
|
|
9
16
|
export async function pack(srcDir: string, dstDir: string) {
|
|
10
17
|
const log = useLogger();
|
|
11
18
|
|
|
12
19
|
const execOptions = {
|
|
20
|
+
cwd: srcDir,
|
|
13
21
|
maxBuffer: 10 * 1024 * 1024,
|
|
14
22
|
};
|
|
15
23
|
|
|
16
|
-
const previousCwd = process.cwd();
|
|
17
|
-
process.chdir(srcDir);
|
|
18
|
-
|
|
19
24
|
/**
|
|
20
25
|
* PNPM pack seems to be a lot faster than NPM pack, so when PNPM is detected
|
|
21
26
|
* we use that instead.
|
|
22
27
|
*/
|
|
23
|
-
const
|
|
28
|
+
const packStdout = shouldUsePnpmPack()
|
|
24
29
|
? await new Promise<string>((resolve, reject) => {
|
|
25
30
|
exec(
|
|
26
31
|
`pnpm pack --pack-destination "${dstDir}"`,
|
|
@@ -28,7 +33,8 @@ export async function pack(srcDir: string, dstDir: string) {
|
|
|
28
33
|
(err, stdout) => {
|
|
29
34
|
if (err) {
|
|
30
35
|
log.error(getErrorMessage(err));
|
|
31
|
-
|
|
36
|
+
reject(err);
|
|
37
|
+
return;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
resolve(stdout);
|
|
@@ -41,7 +47,8 @@ export async function pack(srcDir: string, dstDir: string) {
|
|
|
41
47
|
execOptions,
|
|
42
48
|
(err, stdout) => {
|
|
43
49
|
if (err) {
|
|
44
|
-
|
|
50
|
+
reject(err);
|
|
51
|
+
return;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
resolve(stdout);
|
|
@@ -49,9 +56,12 @@ export async function pack(srcDir: string, dstDir: string) {
|
|
|
49
56
|
);
|
|
50
57
|
});
|
|
51
58
|
|
|
52
|
-
const lastLine =
|
|
59
|
+
const lastLine = packStdout.trim().split("\n").at(-1);
|
|
53
60
|
|
|
54
|
-
assert(
|
|
61
|
+
assert(
|
|
62
|
+
lastLine,
|
|
63
|
+
`Failed to parse last line from stdout: ${packStdout.trim()}`,
|
|
64
|
+
);
|
|
55
65
|
|
|
56
66
|
const fileName = path.basename(lastLine);
|
|
57
67
|
|
|
@@ -59,20 +69,20 @@ export async function pack(srcDir: string, dstDir: string) {
|
|
|
59
69
|
|
|
60
70
|
const filePath = path.join(dstDir, fileName);
|
|
61
71
|
|
|
62
|
-
if (!fs.existsSync(filePath)) {
|
|
63
|
-
log.error(
|
|
64
|
-
`The response from pack could not be resolved to an existing file: ${filePath}`,
|
|
65
|
-
);
|
|
66
|
-
} else {
|
|
67
|
-
log.debug(`Packed (temp)/${fileName}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
process.chdir(previousCwd);
|
|
71
|
-
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
73
|
+
* `pnpm pack` (and occasionally `npm pack`) can return before the tarball is
|
|
74
|
+
* fully visible/flushed to disk. A naive `existsSync` check is not enough:
|
|
75
|
+
* the directory entry can appear before the file's data has been written,
|
|
76
|
+
* which causes downstream consumers (gunzip + tar) to fail with
|
|
77
|
+
* "unexpected end of file". Wait until the file exists and its size has
|
|
78
|
+
* stopped changing across two consecutive polls before returning.
|
|
76
79
|
*/
|
|
80
|
+
await waitForCompleteFile(filePath, {
|
|
81
|
+
timeoutMs: PACK_FILE_READY_TIMEOUT_MS,
|
|
82
|
+
pollMs: PACK_FILE_READY_POLL_MS,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
log.debug(`Packed (temp)/${fileName}`);
|
|
86
|
+
|
|
77
87
|
return filePath;
|
|
78
88
|
}
|