isolate-package 1.31.0 → 1.32.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/{isolate-DtNAHzfa.mjs → isolate-BRD2AgVJ.mjs} +385 -123
- package/dist/isolate-BRD2AgVJ.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/package.json +1 -1
- package/src/isolate.ts +1 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package-lock.json +82 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package.json +8 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/api/package.json +12 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/shared/package.json +12 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/utils/package.json +11 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package-lock.json +56 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package.json +8 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/api/package.json +11 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/other/package.json +11 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +243 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +604 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +417 -21
- package/src/lib/lockfile/process-lockfile.test.ts +4 -0
- package/src/lib/lockfile/process-lockfile.ts +14 -16
- package/src/lib/patches/copy-patches.test.ts +78 -0
- package/src/lib/patches/copy-patches.ts +22 -1
- package/src/lib/registry/collect-reachable-package-names.test.ts +239 -0
- package/src/lib/registry/collect-reachable-package-names.ts +60 -0
- package/src/lib/registry/index.ts +1 -0
- package/src/lib/utils/filter-patched-dependencies.test.ts +77 -0
- package/src/lib/utils/filter-patched-dependencies.ts +41 -17
- package/dist/isolate-DtNAHzfa.mjs.map +0 -1
|
@@ -2,50 +2,446 @@ import Arborist from "@npmcli/arborist";
|
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { useLogger } from "~/lib/logger";
|
|
5
|
+
import type { PackageManifest, PackagesRegistry } from "~/lib/types";
|
|
5
6
|
import { getErrorMessage } from "~/lib/utils";
|
|
6
7
|
import { loadNpmConfig } from "./load-npm-config";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Subset of a package-lock.json v2/v3 `packages[location]` entry that we
|
|
11
|
+
* care about when rewriting. Arborist / npm preserve any additional fields
|
|
12
|
+
* we don't enumerate here via object spread.
|
|
13
|
+
*/
|
|
14
|
+
type LockfilePackageEntry = {
|
|
15
|
+
name?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
resolved?: string;
|
|
18
|
+
integrity?: string;
|
|
19
|
+
link?: boolean;
|
|
20
|
+
dev?: boolean;
|
|
21
|
+
optional?: boolean;
|
|
22
|
+
peer?: boolean;
|
|
23
|
+
devOptional?: boolean;
|
|
24
|
+
extraneous?: boolean;
|
|
25
|
+
dependencies?: Record<string, string>;
|
|
26
|
+
devDependencies?: Record<string, string>;
|
|
27
|
+
optionalDependencies?: Record<string, string>;
|
|
28
|
+
peerDependencies?: Record<string, string>;
|
|
29
|
+
peerDependenciesMeta?: Record<string, unknown>;
|
|
30
|
+
bundleDependencies?: string[] | boolean;
|
|
31
|
+
workspaces?: string[] | Record<string, unknown>;
|
|
32
|
+
engines?: Record<string, string>;
|
|
33
|
+
os?: string[];
|
|
34
|
+
cpu?: string[];
|
|
35
|
+
libc?: string[];
|
|
36
|
+
bin?: Record<string, string> | string;
|
|
37
|
+
funding?: unknown;
|
|
38
|
+
license?: string;
|
|
39
|
+
hasInstallScript?: boolean;
|
|
40
|
+
inBundle?: boolean;
|
|
41
|
+
deprecated?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type NpmLockfile = {
|
|
45
|
+
name?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
lockfileVersion: number;
|
|
48
|
+
requires?: boolean;
|
|
49
|
+
packages: Record<string, LockfilePackageEntry>;
|
|
50
|
+
overrides?: Record<string, unknown>;
|
|
51
|
+
/** Legacy v2 nested-tree representation; dropped when emitting the isolate lockfile. */
|
|
52
|
+
dependencies?: unknown;
|
|
53
|
+
/** Allow unknown top-level fields to flow through. */
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Minimal node shape we consume from Arborist. Kept narrow so the pure JSON
|
|
59
|
+
* rewriter can be tested without instantiating a real tree.
|
|
60
|
+
*/
|
|
61
|
+
export type ReachableNode = {
|
|
62
|
+
location: string;
|
|
63
|
+
isLink: boolean;
|
|
64
|
+
target?: { location: string };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate an isolated NPM lockfile for the target package.
|
|
69
|
+
*
|
|
70
|
+
* When a root `package-lock.json` exists we preserve original resolved
|
|
71
|
+
* versions and integrity by copying entries verbatim from the source
|
|
72
|
+
* lockfile. When it doesn't (forceNpm from pnpm/bun/yarn or modern-Yarn
|
|
73
|
+
* fallback), we fall back to Arborist's `buildIdealTree` against the
|
|
74
|
+
* isolate directory, which matches the prior behaviour.
|
|
12
75
|
*/
|
|
13
76
|
export async function generateNpmLockfile({
|
|
14
77
|
workspaceRootDir,
|
|
15
78
|
isolateDir,
|
|
79
|
+
targetPackageName,
|
|
80
|
+
targetPackageManifest,
|
|
81
|
+
packagesRegistry,
|
|
82
|
+
internalDepPackageNames,
|
|
16
83
|
}: {
|
|
17
84
|
workspaceRootDir: string;
|
|
18
85
|
isolateDir: string;
|
|
86
|
+
targetPackageName: string;
|
|
87
|
+
targetPackageManifest: PackageManifest;
|
|
88
|
+
packagesRegistry: PackagesRegistry;
|
|
89
|
+
internalDepPackageNames: string[];
|
|
19
90
|
}) {
|
|
20
91
|
const log = useLogger();
|
|
21
92
|
|
|
22
|
-
|
|
93
|
+
try {
|
|
94
|
+
const rootLockfilePath = path.join(workspaceRootDir, "package-lock.json");
|
|
23
95
|
|
|
24
|
-
|
|
96
|
+
if (fs.existsSync(rootLockfilePath)) {
|
|
97
|
+
log.debug("Generating NPM lockfile from root package-lock.json...");
|
|
98
|
+
await generateFromRootLockfile({
|
|
99
|
+
workspaceRootDir,
|
|
100
|
+
isolateDir,
|
|
101
|
+
targetPackageName,
|
|
102
|
+
targetPackageManifest,
|
|
103
|
+
packagesRegistry,
|
|
104
|
+
internalDepPackageNames,
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
log.debug(
|
|
108
|
+
"No root package-lock.json found; falling back to buildIdealTree generation",
|
|
109
|
+
);
|
|
110
|
+
await generateViaBuildIdealTree({ workspaceRootDir, isolateDir });
|
|
111
|
+
}
|
|
25
112
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
113
|
+
log.debug(
|
|
114
|
+
"Created lockfile at",
|
|
115
|
+
path.join(isolateDir, "package-lock.json"),
|
|
116
|
+
);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
log.error(`Failed to generate lockfile: ${getErrorMessage(err)}`);
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function generateFromRootLockfile({
|
|
124
|
+
workspaceRootDir,
|
|
125
|
+
isolateDir,
|
|
126
|
+
targetPackageName,
|
|
127
|
+
targetPackageManifest,
|
|
128
|
+
packagesRegistry,
|
|
129
|
+
internalDepPackageNames,
|
|
130
|
+
}: {
|
|
131
|
+
workspaceRootDir: string;
|
|
132
|
+
isolateDir: string;
|
|
133
|
+
targetPackageName: string;
|
|
134
|
+
targetPackageManifest: PackageManifest;
|
|
135
|
+
packagesRegistry: PackagesRegistry;
|
|
136
|
+
internalDepPackageNames: string[];
|
|
137
|
+
}) {
|
|
138
|
+
const log = useLogger();
|
|
139
|
+
|
|
140
|
+
const config = await loadNpmConfig({ npmPath: workspaceRootDir });
|
|
141
|
+
|
|
142
|
+
const arborist = new Arborist({
|
|
143
|
+
path: workspaceRootDir,
|
|
144
|
+
...config.flat,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* `loadVirtual` hydrates every Node with `resolved` and `integrity` taken
|
|
149
|
+
* directly from the lockfile entries. It performs no registry calls.
|
|
150
|
+
*/
|
|
151
|
+
const rootTree = await arborist.loadVirtual();
|
|
152
|
+
|
|
153
|
+
const workspaceNodes = arborist.workspaceNodes(rootTree, [targetPackageName]);
|
|
154
|
+
const targetImporterNode = workspaceNodes[0];
|
|
155
|
+
|
|
156
|
+
if (!targetImporterNode) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Target workspace "${targetPackageName}" not found in root package-lock.json`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof targetImporterNode.location !== "string") {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Target workspace "${targetPackageName}" resolved to a node without a location`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* `workspaceDependencySet` walks `edgesOut` from each seed node. It does
|
|
170
|
+
* not add the seed node itself to the result, so ensure the target
|
|
171
|
+
* importer is included.
|
|
172
|
+
*/
|
|
173
|
+
const reachableNodes = arborist.workspaceDependencySet(
|
|
174
|
+
rootTree,
|
|
175
|
+
[targetPackageName],
|
|
176
|
+
false,
|
|
177
|
+
);
|
|
178
|
+
reachableNodes.add(targetImporterNode);
|
|
179
|
+
|
|
180
|
+
const srcData = rootTree.meta?.data as NpmLockfile | undefined;
|
|
181
|
+
if (
|
|
182
|
+
!srcData ||
|
|
183
|
+
!srcData.packages ||
|
|
184
|
+
Object.keys(srcData.packages).length === 0
|
|
185
|
+
) {
|
|
186
|
+
/**
|
|
187
|
+
* Arborist normalises v1 lockfiles to v3 in `loadVirtual`, but fall
|
|
188
|
+
* back defensively if the virtual tree still has no `packages` map
|
|
189
|
+
* (e.g. an unusual lockfile shape). The fallback generator reads
|
|
190
|
+
* node_modules and won't preserve original versions, but it will
|
|
191
|
+
* produce a valid lockfile rather than failing.
|
|
192
|
+
*/
|
|
193
|
+
useLogger().debug(
|
|
194
|
+
"Source lockfile has no `packages` map; falling back to buildIdealTree",
|
|
195
|
+
);
|
|
196
|
+
await generateViaBuildIdealTree({ workspaceRootDir, isolateDir });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const reachable: ReachableNode[] = [...reachableNodes].map((node) => ({
|
|
201
|
+
location: node.location,
|
|
202
|
+
isLink: node.isLink,
|
|
203
|
+
target: node.target ? { location: node.target.location } : undefined,
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
const internalDepLocs = new Map<string, string>();
|
|
207
|
+
for (const depName of internalDepPackageNames) {
|
|
208
|
+
const pkg = packagesRegistry[depName];
|
|
209
|
+
if (!pkg) {
|
|
210
|
+
throw new Error(`Package ${depName} not found in packages registry`);
|
|
211
|
+
}
|
|
212
|
+
internalDepLocs.set(depName, toPosix(pkg.rootRelativeDir));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const out = buildIsolatedLockfileJson({
|
|
216
|
+
srcData,
|
|
217
|
+
reachable,
|
|
218
|
+
targetImporterLoc: targetImporterNode.location,
|
|
219
|
+
/**
|
|
220
|
+
* npm's lockfile exposes each workspace as a Link at
|
|
221
|
+
* `node_modules/<name>`. This link is pointless in the isolate (the
|
|
222
|
+
* target becomes the root), so filter it out if it shows up in the
|
|
223
|
+
* reachable set.
|
|
224
|
+
*/
|
|
225
|
+
targetLinkLoc: `node_modules/${targetPackageName}`,
|
|
226
|
+
targetPackageManifest,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Overlay each internal dep's adapted manifest onto its lockfile entry
|
|
231
|
+
* so cross-internal-dep references use `file:` instead of `workspace:*`.
|
|
232
|
+
*/
|
|
233
|
+
for (const [, depLoc] of internalDepLocs) {
|
|
234
|
+
if (!out.packages[depLoc]) continue;
|
|
235
|
+
const adaptedManifestPath = path.join(isolateDir, depLoc, "package.json");
|
|
236
|
+
if (!fs.existsSync(adaptedManifestPath)) {
|
|
237
|
+
log.debug(
|
|
238
|
+
`Adapted internal dep manifest missing at ${adaptedManifestPath}; leaving lockfile entry unchanged`,
|
|
239
|
+
);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const adapted = (await fs.readJson(adaptedManifestPath)) as PackageManifest;
|
|
243
|
+
overlayManifestDeps(out.packages[depLoc], adapted);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const outPath = path.join(isolateDir, "package-lock.json");
|
|
247
|
+
await fs.writeFile(outPath, JSON.stringify(out, null, 2) + "\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Pure JSON rewrite of the source lockfile into an isolated lockfile.
|
|
252
|
+
* Extracted so it can be unit tested without mocking Arborist.
|
|
253
|
+
*/
|
|
254
|
+
export function buildIsolatedLockfileJson({
|
|
255
|
+
srcData,
|
|
256
|
+
reachable,
|
|
257
|
+
targetImporterLoc,
|
|
258
|
+
targetLinkLoc,
|
|
259
|
+
targetPackageManifest,
|
|
260
|
+
}: {
|
|
261
|
+
srcData: NpmLockfile;
|
|
262
|
+
reachable: ReachableNode[];
|
|
263
|
+
/** Source location of the target workspace's real importer (e.g. "packages/app") */
|
|
264
|
+
targetImporterLoc: string;
|
|
265
|
+
/** Source location of the target workspace's Link (e.g. "node_modules/app") */
|
|
266
|
+
targetLinkLoc: string;
|
|
267
|
+
targetPackageManifest: PackageManifest;
|
|
268
|
+
}): NpmLockfile {
|
|
269
|
+
const outPackages: Record<string, LockfilePackageEntry> = {};
|
|
270
|
+
const srcPackages = srcData.packages;
|
|
271
|
+
|
|
272
|
+
if (!srcPackages[targetImporterLoc]) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Source lockfile has no entry for target importer "${targetImporterLoc}"`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const targetNestedNodeModulesPrefix = `${targetImporterLoc}/node_modules/`;
|
|
279
|
+
|
|
280
|
+
/** Track the source location each output entry came from, so we can
|
|
281
|
+
* produce a clear error if two source paths remap to the same target.
|
|
282
|
+
*/
|
|
283
|
+
const origLocByNewLoc = new Map<string, string>();
|
|
284
|
+
|
|
285
|
+
for (const node of reachable) {
|
|
286
|
+
const origLoc = node.location;
|
|
287
|
+
|
|
288
|
+
/** The target's self-link has no place in the isolate (root IS the target). */
|
|
289
|
+
if (origLoc === targetLinkLoc) continue;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The target workspace becomes the isolate root, so:
|
|
293
|
+
* "packages/app" -> ""
|
|
294
|
+
* "packages/app/node_modules/<name>" -> "node_modules/<name>"
|
|
295
|
+
* "packages/app/node_modules/a/node_modules/b" -> "node_modules/a/node_modules/b"
|
|
296
|
+
*
|
|
297
|
+
* Only `node_modules` subpaths under the target are remapped — other
|
|
298
|
+
* paths (e.g. a nested workspace importer like
|
|
299
|
+
* `packages/app/lib/core`) are preserved verbatim because their disk
|
|
300
|
+
* location in the isolate is unchanged.
|
|
301
|
+
*/
|
|
302
|
+
let newLoc: string;
|
|
303
|
+
if (origLoc === targetImporterLoc) {
|
|
304
|
+
newLoc = "";
|
|
305
|
+
} else if (origLoc.startsWith(targetNestedNodeModulesPrefix)) {
|
|
306
|
+
newLoc = origLoc.slice(targetImporterLoc.length + 1);
|
|
307
|
+
} else {
|
|
308
|
+
newLoc = origLoc;
|
|
29
309
|
}
|
|
30
310
|
|
|
31
|
-
const
|
|
311
|
+
const srcEntry = srcPackages[origLoc];
|
|
312
|
+
if (!srcEntry) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Reachable node "${origLoc}" has no entry in source lockfile packages`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
32
317
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
318
|
+
const existing = outPackages[newLoc];
|
|
319
|
+
if (existing && !entriesAreEquivalent(existing, srcEntry)) {
|
|
320
|
+
const previousOrigLoc = origLocByNewLoc.get(newLoc) ?? "<unknown>";
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Path collision at "${newLoc}": source locations "${previousOrigLoc}" and "${origLoc}" both map there with conflicting entries. ` +
|
|
323
|
+
`This happens when the target pins a nested version override that collides with a hoisted version still needed by another reachable dependency. ` +
|
|
324
|
+
`Please report a reproduction at https://github.com/0x80/isolate-package/issues.`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
37
327
|
|
|
38
|
-
|
|
328
|
+
outPackages[newLoc] = { ...srcEntry };
|
|
329
|
+
origLocByNewLoc.set(newLoc, origLoc);
|
|
330
|
+
}
|
|
39
331
|
|
|
40
|
-
|
|
332
|
+
/**
|
|
333
|
+
* If the target importer didn't make it into the reachable set for any
|
|
334
|
+
* reason (upstream Arborist bug, programmer error), bail loudly rather
|
|
335
|
+
* than emit a synthesised root entry with no source metadata.
|
|
336
|
+
*/
|
|
337
|
+
if (!outPackages[""]) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Target importer "${targetImporterLoc}" was not present in the reachable node set; cannot construct isolate root entry`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
41
342
|
|
|
42
|
-
|
|
343
|
+
/** Overlay the isolate root with the adapted target manifest. */
|
|
344
|
+
const rootEntry: LockfilePackageEntry = { ...outPackages[""] };
|
|
345
|
+
rootEntry.name = targetPackageManifest.name;
|
|
346
|
+
if (targetPackageManifest.version) {
|
|
347
|
+
rootEntry.version = targetPackageManifest.version;
|
|
348
|
+
}
|
|
349
|
+
overlayManifestDeps(rootEntry, targetPackageManifest);
|
|
350
|
+
/** The isolate is no longer a workspace root. */
|
|
351
|
+
delete rootEntry.workspaces;
|
|
352
|
+
outPackages[""] = rootEntry;
|
|
43
353
|
|
|
44
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Spread unknown top-level fields from the source lockfile so future
|
|
356
|
+
* npm-introduced metadata survives isolation. Then override identity
|
|
357
|
+
* fields and the recomputed `packages`, and drop the legacy
|
|
358
|
+
* `dependencies` tree which would be stale now that `packages` has
|
|
359
|
+
* been subsetted.
|
|
360
|
+
*/
|
|
361
|
+
const out: NpmLockfile = {
|
|
362
|
+
...srcData,
|
|
363
|
+
name: targetPackageManifest.name,
|
|
364
|
+
version: targetPackageManifest.version,
|
|
365
|
+
lockfileVersion: srcData.lockfileVersion ?? 3,
|
|
366
|
+
packages: outPackages,
|
|
367
|
+
};
|
|
368
|
+
/**
|
|
369
|
+
* `requires` is propagated via the `...srcData` spread when the source
|
|
370
|
+
* has it. Don't invent one when the source omitted it — that would be
|
|
371
|
+
* an unnecessary diff from the original lockfile shape.
|
|
372
|
+
*/
|
|
373
|
+
if (srcData.requires === undefined) {
|
|
374
|
+
delete out.requires;
|
|
375
|
+
}
|
|
376
|
+
delete out.dependencies;
|
|
45
377
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Two source entries that map to the same output location are only
|
|
383
|
+
* "equivalent" if they install identical content. We compare the fields
|
|
384
|
+
* that actually determine what npm fetches and stores — version, resolved
|
|
385
|
+
* URL, integrity, and the link flag for workspace links.
|
|
386
|
+
*/
|
|
387
|
+
function entriesAreEquivalent(
|
|
388
|
+
a: LockfilePackageEntry,
|
|
389
|
+
b: LockfilePackageEntry,
|
|
390
|
+
): boolean {
|
|
391
|
+
return (
|
|
392
|
+
a.version === b.version &&
|
|
393
|
+
a.resolved === b.resolved &&
|
|
394
|
+
a.integrity === b.integrity &&
|
|
395
|
+
!!a.link === !!b.link
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function overlayManifestDeps(
|
|
400
|
+
entry: LockfilePackageEntry,
|
|
401
|
+
manifest: PackageManifest,
|
|
402
|
+
) {
|
|
403
|
+
const fields = [
|
|
404
|
+
"dependencies",
|
|
405
|
+
"devDependencies",
|
|
406
|
+
"optionalDependencies",
|
|
407
|
+
"peerDependencies",
|
|
408
|
+
] as const;
|
|
409
|
+
for (const field of fields) {
|
|
410
|
+
const value = manifest[field];
|
|
411
|
+
if (value) {
|
|
412
|
+
entry[field] = value;
|
|
413
|
+
} else {
|
|
414
|
+
delete entry[field];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function toPosix(p: string): string {
|
|
420
|
+
return p.split(path.sep).join(path.posix.sep);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function generateViaBuildIdealTree({
|
|
424
|
+
workspaceRootDir,
|
|
425
|
+
isolateDir,
|
|
426
|
+
}: {
|
|
427
|
+
workspaceRootDir: string;
|
|
428
|
+
isolateDir: string;
|
|
429
|
+
}) {
|
|
430
|
+
const nodeModulesPath = path.join(workspaceRootDir, "node_modules");
|
|
431
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
432
|
+
throw new Error(`Failed to find node_modules at ${nodeModulesPath}`);
|
|
50
433
|
}
|
|
434
|
+
|
|
435
|
+
const config = await loadNpmConfig({ npmPath: workspaceRootDir });
|
|
436
|
+
|
|
437
|
+
const arborist = new Arborist({
|
|
438
|
+
path: isolateDir,
|
|
439
|
+
...config.flat,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const { meta } = await arborist.buildIdealTree();
|
|
443
|
+
meta?.commit();
|
|
444
|
+
|
|
445
|
+
const lockfilePath = path.join(isolateDir, "package-lock.json");
|
|
446
|
+
await fs.writeFile(lockfilePath, String(meta));
|
|
51
447
|
}
|
|
@@ -72,6 +72,10 @@ describe("processLockfile", () => {
|
|
|
72
72
|
expect(generateNpmLockfile).toHaveBeenCalledWith({
|
|
73
73
|
workspaceRootDir: "/workspace",
|
|
74
74
|
isolateDir: "/workspace/apps/my-app/isolate",
|
|
75
|
+
targetPackageName: "my-app",
|
|
76
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
77
|
+
packagesRegistry: {},
|
|
78
|
+
internalDepPackageNames: [],
|
|
75
79
|
});
|
|
76
80
|
expect(result).toBe(false);
|
|
77
81
|
});
|
|
@@ -22,6 +22,7 @@ export async function processLockfile({
|
|
|
22
22
|
isolateDir,
|
|
23
23
|
internalDepPackageNames,
|
|
24
24
|
targetPackageDir,
|
|
25
|
+
targetPackageName,
|
|
25
26
|
targetPackageManifest,
|
|
26
27
|
patchedDependencies,
|
|
27
28
|
config,
|
|
@@ -39,13 +40,19 @@ export async function processLockfile({
|
|
|
39
40
|
}) {
|
|
40
41
|
const log = useLogger();
|
|
41
42
|
|
|
43
|
+
const npmGeneratorParams = {
|
|
44
|
+
workspaceRootDir,
|
|
45
|
+
isolateDir,
|
|
46
|
+
targetPackageName,
|
|
47
|
+
targetPackageManifest,
|
|
48
|
+
packagesRegistry,
|
|
49
|
+
internalDepPackageNames,
|
|
50
|
+
};
|
|
51
|
+
|
|
42
52
|
if (config.forceNpm) {
|
|
43
53
|
log.debug("Forcing to use NPM for isolate output");
|
|
44
54
|
|
|
45
|
-
await generateNpmLockfile(
|
|
46
|
-
workspaceRootDir,
|
|
47
|
-
isolateDir,
|
|
48
|
-
});
|
|
55
|
+
await generateNpmLockfile(npmGeneratorParams);
|
|
49
56
|
|
|
50
57
|
return true;
|
|
51
58
|
}
|
|
@@ -55,10 +62,7 @@ export async function processLockfile({
|
|
|
55
62
|
|
|
56
63
|
switch (name) {
|
|
57
64
|
case "npm": {
|
|
58
|
-
await generateNpmLockfile(
|
|
59
|
-
workspaceRootDir,
|
|
60
|
-
isolateDir,
|
|
61
|
-
});
|
|
65
|
+
await generateNpmLockfile(npmGeneratorParams);
|
|
62
66
|
|
|
63
67
|
break;
|
|
64
68
|
}
|
|
@@ -73,10 +77,7 @@ export async function processLockfile({
|
|
|
73
77
|
"Detected modern version of Yarn. Using NPM lockfile fallback.",
|
|
74
78
|
);
|
|
75
79
|
|
|
76
|
-
await generateNpmLockfile(
|
|
77
|
-
workspaceRootDir,
|
|
78
|
-
isolateDir,
|
|
79
|
-
});
|
|
80
|
+
await generateNpmLockfile(npmGeneratorParams);
|
|
80
81
|
|
|
81
82
|
usedFallbackToNpm = true;
|
|
82
83
|
}
|
|
@@ -112,10 +113,7 @@ export async function processLockfile({
|
|
|
112
113
|
log.warn(
|
|
113
114
|
`Unexpected package manager ${name as string}. Using NPM for output`,
|
|
114
115
|
);
|
|
115
|
-
await generateNpmLockfile(
|
|
116
|
-
workspaceRootDir,
|
|
117
|
-
isolateDir,
|
|
118
|
-
});
|
|
116
|
+
await generateNpmLockfile(npmGeneratorParams);
|
|
119
117
|
|
|
120
118
|
usedFallbackToNpm = true;
|
|
121
119
|
}
|