isolate-package 1.30.0-0 → 1.31.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.d.mts +14 -2
- package/dist/index.mjs +2 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{isolate-BZjQHi0U.mjs → isolate-DtNAHzfa.mjs} +122 -71
- package/dist/isolate-DtNAHzfa.mjs.map +1 -0
- package/dist/isolate-bin.mjs +5 -7
- package/dist/isolate-bin.mjs.map +1 -1
- package/package.json +31 -21
- package/src/index.ts +6 -0
- package/src/isolate-bin.ts +2 -2
- package/src/isolate.ts +42 -5
- package/src/lib/config.ts +31 -6
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +17 -3
- package/src/lib/manifest/adapt-target-package-manifest.ts +2 -1
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +181 -0
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +17 -5
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +61 -0
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +42 -3
- package/src/lib/manifest/validate-manifest.test.ts +19 -10
- package/src/lib/manifest/validate-manifest.ts +13 -1
- package/src/lib/patches/copy-patches.test.ts +316 -243
- package/src/lib/patches/copy-patches.ts +54 -15
- package/src/lib/types.ts +6 -0
- package/src/lib/utils/is-rush-workspace.ts +6 -0
- package/dist/isolate-BZjQHi0U.mjs.map +0 -1
package/src/lib/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { detectMonorepo } from "detect-monorepo";
|
|
2
3
|
import fs from "fs-extra";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
@@ -14,7 +15,13 @@ export type IsolateConfigResolved = {
|
|
|
14
15
|
targetPackagePath?: string;
|
|
15
16
|
tsconfigPath: string;
|
|
16
17
|
workspacePackages?: string[];
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Path to the workspace root, relative to the target package directory.
|
|
20
|
+
* When omitted, the workspace root is auto-detected by walking upward from
|
|
21
|
+
* the target package directory looking for a pnpm-workspace.yaml, a
|
|
22
|
+
* package.json with a `workspaces` field, or a rush.json.
|
|
23
|
+
*/
|
|
24
|
+
workspaceRoot?: string;
|
|
18
25
|
forceNpm: boolean;
|
|
19
26
|
pickFromScripts?: string[];
|
|
20
27
|
omitFromScripts?: string[];
|
|
@@ -31,7 +38,7 @@ const configDefaults: IsolateConfigResolved = {
|
|
|
31
38
|
targetPackagePath: undefined,
|
|
32
39
|
tsconfigPath: "./tsconfig.json",
|
|
33
40
|
workspacePackages: undefined,
|
|
34
|
-
workspaceRoot:
|
|
41
|
+
workspaceRoot: undefined,
|
|
35
42
|
forceNpm: false,
|
|
36
43
|
pickFromScripts: undefined,
|
|
37
44
|
omitFromScripts: undefined,
|
|
@@ -170,17 +177,35 @@ function validateConfig(config: IsolateConfig) {
|
|
|
170
177
|
* Resolve the target package directory and workspace root directory from the
|
|
171
178
|
* configuration. When targetPackagePath is set, the config is assumed to live
|
|
172
179
|
* at the workspace root. Otherwise it lives in the target package directory.
|
|
180
|
+
*
|
|
181
|
+
* When `workspaceRoot` is not explicitly set, auto-detect the monorepo root by
|
|
182
|
+
* walking upward from the target package directory.
|
|
173
183
|
*/
|
|
174
184
|
export function resolveWorkspacePaths(config: IsolateConfigResolved) {
|
|
175
185
|
const targetPackageDir = config.targetPackagePath
|
|
176
186
|
? path.join(process.cwd(), config.targetPackagePath)
|
|
177
187
|
: process.cwd();
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
189
|
+
if (config.targetPackagePath) {
|
|
190
|
+
return { targetPackageDir, workspaceRootDir: process.cwd() };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (config.workspaceRoot !== undefined) {
|
|
194
|
+
return {
|
|
195
|
+
targetPackageDir,
|
|
196
|
+
workspaceRootDir: path.join(targetPackageDir, config.workspaceRoot),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const detected = detectMonorepo(targetPackageDir);
|
|
201
|
+
|
|
202
|
+
if (!detected) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Failed to auto-detect monorepo workspace root from ${targetPackageDir}. Set the 'workspaceRoot' config option explicitly.`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
182
207
|
|
|
183
|
-
return { targetPackageDir, workspaceRootDir };
|
|
208
|
+
return { targetPackageDir, workspaceRootDir: detected.rootDir };
|
|
184
209
|
}
|
|
185
210
|
|
|
186
211
|
export function resolveConfig(
|
|
@@ -211,7 +211,11 @@ export async function generateBunLockfile({
|
|
|
211
211
|
const internalDepWorkspaceKeys = new Map<string, string>();
|
|
212
212
|
for (const name of internalDepPackageNames) {
|
|
213
213
|
const pkg = got(packagesRegistry, name);
|
|
214
|
-
|
|
214
|
+
/** Normalize to POSIX separators for matching bun.lock workspace keys */
|
|
215
|
+
const workspaceKey = pkg.rootRelativeDir
|
|
216
|
+
.split(path.sep)
|
|
217
|
+
.join(path.posix.sep);
|
|
218
|
+
internalDepWorkspaceKeys.set(name, workspaceKey);
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
/** Build the filtered workspaces object */
|
|
@@ -219,7 +223,13 @@ export async function generateBunLockfile({
|
|
|
219
223
|
|
|
220
224
|
/** Remap the target workspace to root ("") */
|
|
221
225
|
const targetEntry = lockfile.workspaces[targetWorkspaceKey];
|
|
222
|
-
if (targetEntry) {
|
|
226
|
+
if (!targetEntry) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Target workspace "${targetWorkspaceKey}" not found in bun.lock. Available workspaces: ${Object.keys(lockfile.workspaces).join(", ")}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
{
|
|
223
233
|
const entry = { ...targetEntry };
|
|
224
234
|
if (!includeDevDependencies) {
|
|
225
235
|
delete entry.devDependencies;
|
|
@@ -330,7 +340,11 @@ export async function generateBunLockfile({
|
|
|
330
340
|
}
|
|
331
341
|
|
|
332
342
|
const outputPath = path.join(isolateDir, "bun.lock");
|
|
333
|
-
|
|
343
|
+
/** Append trailing newline to match Bun's native output format */
|
|
344
|
+
await fs.writeFile(
|
|
345
|
+
outputPath,
|
|
346
|
+
serializeWithTrailingCommas(outputLockfile) + "\n",
|
|
347
|
+
);
|
|
334
348
|
|
|
335
349
|
log.debug("Created lockfile at", outputPath);
|
|
336
350
|
} catch (err) {
|
|
@@ -56,7 +56,8 @@ export async function adaptTargetPackageManifest({
|
|
|
56
56
|
? /**
|
|
57
57
|
* For PNPM and Bun the output itself is a workspace so we can preserve
|
|
58
58
|
* the specifiers with "workspace:*" in the output manifest, but we do
|
|
59
|
-
* want to adopt
|
|
59
|
+
* want to adopt workspace-level fields from the root package.json
|
|
60
|
+
* (pnpm.overrides for PNPM, top-level overrides for Bun).
|
|
60
61
|
*/
|
|
61
62
|
await adoptPnpmFieldsFromRoot(
|
|
62
63
|
manifestWithResolvedCatalogs,
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import type { PackageManifest, PackagesRegistry } from "~/lib/types";
|
|
3
|
+
|
|
4
|
+
/** Mock dependencies */
|
|
5
|
+
vi.mock("~/lib/package-manager", () => ({
|
|
6
|
+
usePackageManager: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("../io", () => ({
|
|
10
|
+
writeManifest: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("./adapt-manifest-internal-deps", () => ({
|
|
14
|
+
adaptManifestInternalDeps: vi.fn(({ manifest }) => manifest),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("./resolve-catalog-dependencies", () => ({
|
|
18
|
+
resolveCatalogDependencies: vi.fn((deps) => Promise.resolve(deps)),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
|
|
22
|
+
|
|
23
|
+
const { writeManifest } = vi.mocked(await import("../io"));
|
|
24
|
+
|
|
25
|
+
const { adaptInternalPackageManifests } =
|
|
26
|
+
await import("./adapt-internal-package-manifests");
|
|
27
|
+
|
|
28
|
+
describe("adaptInternalPackageManifests", () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
usePackageManager.mockReturnValue({
|
|
32
|
+
name: "pnpm",
|
|
33
|
+
version: "9.0.0",
|
|
34
|
+
majorVersion: 9,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function createRegistry(
|
|
39
|
+
entries: Record<
|
|
40
|
+
string,
|
|
41
|
+
{ rootRelativeDir: string; manifest: PackageManifest }
|
|
42
|
+
>,
|
|
43
|
+
): PackagesRegistry {
|
|
44
|
+
const registry: PackagesRegistry = {};
|
|
45
|
+
for (const [name, { rootRelativeDir, manifest }] of Object.entries(
|
|
46
|
+
entries,
|
|
47
|
+
)) {
|
|
48
|
+
registry[name] = {
|
|
49
|
+
absoluteDir: `/workspace/${rootRelativeDir}`,
|
|
50
|
+
rootRelativeDir,
|
|
51
|
+
manifest,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return registry;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
it("should preserve scripts in internal dependency manifests", async () => {
|
|
58
|
+
const manifest: PackageManifest = {
|
|
59
|
+
name: "@repo/database",
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
scripts: {
|
|
62
|
+
postinstall: "prisma generate",
|
|
63
|
+
build: "tsc",
|
|
64
|
+
},
|
|
65
|
+
dependencies: {
|
|
66
|
+
prisma: "^5.0.0",
|
|
67
|
+
},
|
|
68
|
+
devDependencies: {
|
|
69
|
+
typescript: "^5.0.0",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const packagesRegistry = createRegistry({
|
|
74
|
+
"@repo/database": {
|
|
75
|
+
rootRelativeDir: "packages/database",
|
|
76
|
+
manifest,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await adaptInternalPackageManifests({
|
|
81
|
+
internalPackageNames: ["@repo/database"],
|
|
82
|
+
packagesRegistry,
|
|
83
|
+
isolateDir: "/output",
|
|
84
|
+
forceNpm: false,
|
|
85
|
+
workspaceRootDir: "/workspace",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(writeManifest).toHaveBeenCalledOnce();
|
|
89
|
+
|
|
90
|
+
const writtenManifest = writeManifest.mock.calls[0]![1];
|
|
91
|
+
|
|
92
|
+
expect(writtenManifest.scripts).toEqual({
|
|
93
|
+
postinstall: "prisma generate",
|
|
94
|
+
build: "tsc",
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should strip the prepare script from internal dependency manifests", async () => {
|
|
99
|
+
const manifest: PackageManifest = {
|
|
100
|
+
name: "@repo/common",
|
|
101
|
+
version: "1.0.0",
|
|
102
|
+
scripts: {
|
|
103
|
+
prepare: "npm run clean && npm run build",
|
|
104
|
+
clean: "del-cli dist",
|
|
105
|
+
build: "tsdown",
|
|
106
|
+
postinstall: "prisma generate",
|
|
107
|
+
},
|
|
108
|
+
dependencies: {
|
|
109
|
+
ky: "^1.0.0",
|
|
110
|
+
},
|
|
111
|
+
devDependencies: {
|
|
112
|
+
"del-cli": "^7.0.0",
|
|
113
|
+
tsdown: "^0.20.0",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const packagesRegistry = createRegistry({
|
|
118
|
+
"@repo/common": {
|
|
119
|
+
rootRelativeDir: "packages/common",
|
|
120
|
+
manifest,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await adaptInternalPackageManifests({
|
|
125
|
+
internalPackageNames: ["@repo/common"],
|
|
126
|
+
packagesRegistry,
|
|
127
|
+
isolateDir: "/output",
|
|
128
|
+
forceNpm: false,
|
|
129
|
+
workspaceRootDir: "/workspace",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(writeManifest).toHaveBeenCalledOnce();
|
|
133
|
+
|
|
134
|
+
const writtenManifest = writeManifest.mock.calls[0]![1];
|
|
135
|
+
|
|
136
|
+
/** prepare should be stripped because it depends on devDependency binaries */
|
|
137
|
+
expect(writtenManifest.scripts?.prepare).toBeUndefined();
|
|
138
|
+
|
|
139
|
+
/** Other scripts should be preserved */
|
|
140
|
+
expect(writtenManifest.scripts).toEqual({
|
|
141
|
+
clean: "del-cli dist",
|
|
142
|
+
build: "tsdown",
|
|
143
|
+
postinstall: "prisma generate",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should strip devDependencies from internal dependency manifests", async () => {
|
|
148
|
+
const manifest: PackageManifest = {
|
|
149
|
+
name: "@repo/shared",
|
|
150
|
+
version: "1.0.0",
|
|
151
|
+
dependencies: {
|
|
152
|
+
lodash: "^4.0.0",
|
|
153
|
+
},
|
|
154
|
+
devDependencies: {
|
|
155
|
+
vitest: "^1.0.0",
|
|
156
|
+
typescript: "^5.0.0",
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const packagesRegistry = createRegistry({
|
|
161
|
+
"@repo/shared": {
|
|
162
|
+
rootRelativeDir: "packages/shared",
|
|
163
|
+
manifest,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await adaptInternalPackageManifests({
|
|
168
|
+
internalPackageNames: ["@repo/shared"],
|
|
169
|
+
packagesRegistry,
|
|
170
|
+
isolateDir: "/output",
|
|
171
|
+
forceNpm: false,
|
|
172
|
+
workspaceRootDir: "/workspace",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(writeManifest).toHaveBeenCalledOnce();
|
|
176
|
+
|
|
177
|
+
const writtenManifest = writeManifest.mock.calls[0]![1];
|
|
178
|
+
|
|
179
|
+
expect(writtenManifest.devDependencies).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -31,8 +31,19 @@ export async function adaptInternalPackageManifests({
|
|
|
31
31
|
internalPackageNames.map(async (packageName) => {
|
|
32
32
|
const { manifest, rootRelativeDir } = got(packagesRegistry, packageName);
|
|
33
33
|
|
|
34
|
-
/** Dev dependencies
|
|
35
|
-
const strippedManifest = omit(manifest, ["
|
|
34
|
+
/** Dev dependencies are never included for internal deps */
|
|
35
|
+
const strippedManifest = omit(manifest, ["devDependencies"]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Strip the `prepare` script because it runs during `pnpm install` and
|
|
39
|
+
* typically depends on devDependency binaries (e.g. tsdown, del-cli)
|
|
40
|
+
* which are not available in the isolated output. Other lifecycle
|
|
41
|
+
* scripts like `postinstall` are preserved because they handle runtime
|
|
42
|
+
* setup (e.g. Prisma client generation).
|
|
43
|
+
*/
|
|
44
|
+
if (strippedManifest.scripts) {
|
|
45
|
+
strippedManifest.scripts = omit(strippedManifest.scripts, ["prepare"]);
|
|
46
|
+
}
|
|
36
47
|
|
|
37
48
|
/** Resolve catalog dependencies before adapting internal deps */
|
|
38
49
|
const manifestWithResolvedCatalogs = {
|
|
@@ -44,10 +55,11 @@ export async function adaptInternalPackageManifests({
|
|
|
44
55
|
};
|
|
45
56
|
|
|
46
57
|
const outputManifest =
|
|
47
|
-
packageManager.name === "pnpm" &&
|
|
58
|
+
(packageManager.name === "pnpm" || packageManager.name === "bun") &&
|
|
59
|
+
!forceNpm
|
|
48
60
|
? /**
|
|
49
|
-
* For PNPM the output itself is a workspace so we can preserve
|
|
50
|
-
* with "workspace:*" in the output manifest.
|
|
61
|
+
* For PNPM and Bun the output itself is a workspace so we can preserve
|
|
62
|
+
* the specifiers with "workspace:*" in the output manifest.
|
|
51
63
|
*/
|
|
52
64
|
manifestWithResolvedCatalogs
|
|
53
65
|
: /** For other package managers we replace the links to internal dependencies */
|
|
@@ -12,10 +12,16 @@ vi.mock("~/lib/utils", () => ({
|
|
|
12
12
|
readTypedJson: vi.fn(),
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
+
vi.mock("~/lib/package-manager", () => ({
|
|
16
|
+
usePackageManager: vi.fn(() => ({ name: "pnpm", majorVersion: 9 })),
|
|
17
|
+
}));
|
|
18
|
+
|
|
15
19
|
const { isRushWorkspace, readTypedJson } = vi.mocked(
|
|
16
20
|
await import("~/lib/utils"),
|
|
17
21
|
);
|
|
18
22
|
|
|
23
|
+
const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
|
|
24
|
+
|
|
19
25
|
describe("adoptPnpmFieldsFromRoot", () => {
|
|
20
26
|
beforeEach(() => {
|
|
21
27
|
vi.clearAllMocks();
|
|
@@ -209,4 +215,59 @@ describe("adoptPnpmFieldsFromRoot", () => {
|
|
|
209
215
|
},
|
|
210
216
|
});
|
|
211
217
|
});
|
|
218
|
+
|
|
219
|
+
it("should adopt top-level overrides for Bun", async () => {
|
|
220
|
+
usePackageManager.mockReturnValue({
|
|
221
|
+
name: "bun",
|
|
222
|
+
majorVersion: 1,
|
|
223
|
+
version: "1.0.0",
|
|
224
|
+
packageManagerString: "bun@1.0.0",
|
|
225
|
+
} as ReturnType<typeof usePackageManager>);
|
|
226
|
+
isRushWorkspace.mockReturnValue(false);
|
|
227
|
+
readTypedJson.mockResolvedValue({
|
|
228
|
+
name: "root",
|
|
229
|
+
version: "1.0.0",
|
|
230
|
+
overrides: {
|
|
231
|
+
foo: "^1.0.0",
|
|
232
|
+
},
|
|
233
|
+
} as unknown as ProjectManifest);
|
|
234
|
+
|
|
235
|
+
const targetManifest: PackageManifest = {
|
|
236
|
+
name: "test-package",
|
|
237
|
+
version: "1.0.0",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = await adoptPnpmFieldsFromRoot(targetManifest, "/workspace");
|
|
241
|
+
|
|
242
|
+
expect(result).toEqual({
|
|
243
|
+
name: "test-package",
|
|
244
|
+
version: "1.0.0",
|
|
245
|
+
overrides: {
|
|
246
|
+
foo: "^1.0.0",
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should return original manifest for Bun when no overrides are present", async () => {
|
|
252
|
+
usePackageManager.mockReturnValue({
|
|
253
|
+
name: "bun",
|
|
254
|
+
majorVersion: 1,
|
|
255
|
+
version: "1.0.0",
|
|
256
|
+
packageManagerString: "bun@1.0.0",
|
|
257
|
+
} as ReturnType<typeof usePackageManager>);
|
|
258
|
+
isRushWorkspace.mockReturnValue(false);
|
|
259
|
+
readTypedJson.mockResolvedValue({
|
|
260
|
+
name: "root",
|
|
261
|
+
version: "1.0.0",
|
|
262
|
+
} as ProjectManifest);
|
|
263
|
+
|
|
264
|
+
const targetManifest: PackageManifest = {
|
|
265
|
+
name: "test-package",
|
|
266
|
+
version: "1.0.0",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const result = await adoptPnpmFieldsFromRoot(targetManifest, "/workspace");
|
|
270
|
+
|
|
271
|
+
expect(result).toEqual(targetManifest);
|
|
272
|
+
});
|
|
212
273
|
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ProjectManifest, PnpmSettings } from "@pnpm/types";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { usePackageManager } from "~/lib/package-manager";
|
|
3
4
|
import type { PackageManifest } from "~/lib/types";
|
|
4
5
|
import { isRushWorkspace, readTypedJson } from "~/lib/utils";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Adopts
|
|
8
|
-
* overrides, onlyBuiltDependencies, and ignoredBuiltDependencies
|
|
9
|
-
*
|
|
8
|
+
* Adopts workspace-level fields from the root package manifest. For pnpm this
|
|
9
|
+
* reads overrides, onlyBuiltDependencies, and ignoredBuiltDependencies from the
|
|
10
|
+
* `pnpm` key. For Bun it reads `overrides` from the top level.
|
|
10
11
|
*/
|
|
11
12
|
export async function adoptPnpmFieldsFromRoot(
|
|
12
13
|
targetPackageManifest: PackageManifest,
|
|
@@ -20,6 +21,44 @@ export async function adoptPnpmFieldsFromRoot(
|
|
|
20
21
|
path.join(workspaceRootDir, "package.json"),
|
|
21
22
|
);
|
|
22
23
|
|
|
24
|
+
const packageManager = usePackageManager();
|
|
25
|
+
|
|
26
|
+
if (packageManager.name === "bun") {
|
|
27
|
+
return adoptBunFieldsFromRoot(targetPackageManifest, rootPackageManifest);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return adoptPnpmFieldsOnly(targetPackageManifest, rootPackageManifest);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Adopt Bun's top-level overrides from the root manifest */
|
|
34
|
+
function adoptBunFieldsFromRoot(
|
|
35
|
+
targetPackageManifest: PackageManifest,
|
|
36
|
+
rootPackageManifest: ProjectManifest,
|
|
37
|
+
): PackageManifest {
|
|
38
|
+
/**
|
|
39
|
+
* Bun supports `overrides` at the top level of package.json (same as npm).
|
|
40
|
+
* Read from the root manifest and set them on the output manifest so that
|
|
41
|
+
* `bun install --frozen-lockfile` succeeds.
|
|
42
|
+
*/
|
|
43
|
+
const overrides = (rootPackageManifest as Record<string, unknown>)[
|
|
44
|
+
"overrides"
|
|
45
|
+
] as Record<string, string> | undefined;
|
|
46
|
+
|
|
47
|
+
if (!overrides) {
|
|
48
|
+
return targetPackageManifest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...targetPackageManifest,
|
|
53
|
+
overrides,
|
|
54
|
+
} as PackageManifest;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Adopt pnpm-specific fields from the root manifest */
|
|
58
|
+
function adoptPnpmFieldsOnly(
|
|
59
|
+
targetPackageManifest: PackageManifest,
|
|
60
|
+
rootPackageManifest: ProjectManifest,
|
|
61
|
+
): PackageManifest {
|
|
23
62
|
const { overrides, onlyBuiltDependencies, ignoredBuiltDependencies } =
|
|
24
63
|
rootPackageManifest.pnpm || {};
|
|
25
64
|
|
|
@@ -43,7 +43,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
43
43
|
|
|
44
44
|
expect(() =>
|
|
45
45
|
validateManifestMandatoryFields(invalidDevManifest, packagePath, false),
|
|
46
|
-
).toThrow(/missing
|
|
46
|
+
).toThrow(/missing the "version" field/);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it("should throw error when version field is missing", () => {
|
|
@@ -54,7 +54,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
54
54
|
|
|
55
55
|
expect(() =>
|
|
56
56
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
57
|
-
).toThrow(/missing
|
|
57
|
+
).toThrow(/missing the "version" field/);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it("should throw error when files field is missing", () => {
|
|
@@ -65,7 +65,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
65
65
|
|
|
66
66
|
expect(() =>
|
|
67
67
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
68
|
-
).toThrow(/missing
|
|
68
|
+
).toThrow(/missing the "files" field/);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it("should throw error when files field is empty array", () => {
|
|
@@ -77,7 +77,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
77
77
|
|
|
78
78
|
expect(() =>
|
|
79
79
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
80
|
-
).toThrow(/missing
|
|
80
|
+
).toThrow(/missing the "files" field/);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it("should throw error when files field is not an array", () => {
|
|
@@ -89,7 +89,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
89
89
|
|
|
90
90
|
expect(() =>
|
|
91
91
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
92
|
-
).toThrow(/missing
|
|
92
|
+
).toThrow(/missing the "files" field/);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
it("should throw error when both fields are missing", () => {
|
|
@@ -99,20 +99,29 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
99
99
|
|
|
100
100
|
expect(() =>
|
|
101
101
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
102
|
-
).toThrow(/missing mandatory fields: version, files/);
|
|
102
|
+
).toThrow(/missing mandatory fields in its package\.json: version, files/);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it("should include
|
|
105
|
+
it("should include documentation URL in error message", () => {
|
|
106
106
|
const invalidManifest = {
|
|
107
107
|
name: "test-package",
|
|
108
108
|
} as PackageManifest;
|
|
109
109
|
|
|
110
110
|
expect(() =>
|
|
111
111
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
112
|
-
).toThrow(
|
|
112
|
+
).toThrow(
|
|
113
|
+
/See https:\/\/isolate-package\.codecompose\.dev\/getting-started#prerequisites/,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const singleFieldManifest = {
|
|
117
|
+
name: "test-package",
|
|
118
|
+
version: "1.0.0",
|
|
119
|
+
} as PackageManifest;
|
|
113
120
|
|
|
114
121
|
expect(() =>
|
|
115
|
-
validateManifestMandatoryFields(
|
|
116
|
-
).toThrow(
|
|
122
|
+
validateManifestMandatoryFields(singleFieldManifest, packagePath),
|
|
123
|
+
).toThrow(
|
|
124
|
+
/See https:\/\/isolate-package\.codecompose\.dev\/getting-started#define-files-field-in-each-package-manifest/,
|
|
125
|
+
);
|
|
117
126
|
});
|
|
118
127
|
});
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { useLogger } from "../logger";
|
|
2
2
|
import type { PackageManifest } from "../types";
|
|
3
3
|
|
|
4
|
+
/** Maps field names to their documentation URLs */
|
|
5
|
+
const fieldDocUrls: Record<string, string> = {
|
|
6
|
+
version:
|
|
7
|
+
"https://isolate-package.codecompose.dev/getting-started#define-version-field-in-each-package-manifest",
|
|
8
|
+
files:
|
|
9
|
+
"https://isolate-package.codecompose.dev/getting-started#define-files-field-in-each-package-manifest",
|
|
10
|
+
};
|
|
11
|
+
|
|
4
12
|
/**
|
|
5
13
|
* Validate that mandatory fields are present in the package manifest. These
|
|
6
14
|
* fields are required for the isolate process to work properly.
|
|
@@ -38,7 +46,11 @@ export function validateManifestMandatoryFields(
|
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
if (missingFields.length > 0) {
|
|
41
|
-
const
|
|
49
|
+
const field = missingFields[0]!;
|
|
50
|
+
const errorMessage =
|
|
51
|
+
missingFields.length === 1
|
|
52
|
+
? `Package at ${packagePath} is missing the "${field}" field in its package.json. See ${fieldDocUrls[field] ?? "https://isolate-package.codecompose.dev/getting-started#prerequisites"}`
|
|
53
|
+
: `Package at ${packagePath} is missing mandatory fields in its package.json: ${missingFields.join(", ")}. See https://isolate-package.codecompose.dev/getting-started#prerequisites`;
|
|
42
54
|
|
|
43
55
|
log.error(errorMessage);
|
|
44
56
|
throw new Error(errorMessage);
|