isolate-package 1.27.0 → 1.28.1
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/README.md +20 -420
- package/dist/index.d.mts +13 -5
- package/dist/index.mjs +21 -2
- package/dist/index.mjs.map +1 -0
- package/dist/{isolate-B11ztsrI.mjs → isolate-D-Qd5BJJ.mjs} +114 -25
- package/dist/isolate-D-Qd5BJJ.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/dist/isolate-bin.mjs.map +1 -1
- package/package.json +37 -32
- package/src/get-internal-package-names.test.ts +213 -0
- package/src/get-internal-package-names.ts +38 -0
- package/src/index.ts +3 -5
- package/src/isolate-bin.ts +1 -1
- package/src/isolate.ts +21 -31
- package/src/lib/cli.test.ts +3 -3
- package/src/lib/cli.ts +4 -4
- package/src/lib/config.test.ts +163 -0
- package/src/lib/config.ts +134 -8
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.ts +6 -6
- package/src/lib/lockfile/helpers/pnpm-map-importer.ts +5 -5
- package/src/lib/lockfile/process-lockfile.ts +3 -3
- package/src/lib/manifest/adapt-target-package-manifest.ts +2 -2
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +3 -3
- package/src/lib/manifest/helpers/adapt-manifest-internal-deps.ts +2 -2
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +1 -1
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +2 -2
- package/src/lib/manifest/helpers/patch-internal-entries.ts +2 -2
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.ts +3 -3
- package/src/lib/manifest/io.ts +2 -2
- package/src/lib/manifest/validate-manifest.test.ts +10 -10
- package/src/lib/manifest/validate-manifest.ts +1 -1
- package/src/lib/output/unpack-dependencies.ts +4 -4
- package/src/lib/package-manager/helpers/infer-from-files.ts +1 -1
- package/src/lib/package-manager/helpers/infer-from-manifest.ts +3 -3
- package/src/lib/package-manager/index.ts +2 -2
- package/src/lib/patches/copy-patches.test.ts +7 -7
- package/src/lib/patches/copy-patches.ts +5 -5
- package/src/lib/registry/create-packages-registry.ts +12 -14
- package/src/lib/registry/helpers/find-packages-globs.ts +7 -7
- package/src/lib/registry/list-internal-packages.test.ts +291 -0
- package/src/lib/registry/list-internal-packages.ts +70 -18
- package/src/lib/utils/filter-object-undefined.test.ts +1 -1
- package/src/lib/utils/filter-object-undefined.ts +1 -1
- package/src/lib/utils/filter-patched-dependencies.ts +2 -2
- package/src/lib/utils/json.ts +4 -4
- package/src/lib/utils/pack.ts +3 -3
- package/src/lib/utils/yaml.ts +1 -1
- package/dist/isolate-B11ztsrI.mjs.map +0 -1
- package/docs/firebase.md +0 -144
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { getInternalPackageNames } from "./get-internal-package-names";
|
|
6
|
+
|
|
7
|
+
vi.mock("~/lib/logger", () => ({
|
|
8
|
+
useLogger: () => ({
|
|
9
|
+
debug: vi.fn(),
|
|
10
|
+
info: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
error: vi.fn(),
|
|
13
|
+
}),
|
|
14
|
+
setLogLevel: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const packageManagerResult = {
|
|
18
|
+
name: "pnpm",
|
|
19
|
+
version: "9.0.0",
|
|
20
|
+
majorVersion: 9,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockDetectPackageManager = vi.fn(
|
|
24
|
+
(_workspaceRootDir: string) => packageManagerResult,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
vi.mock("~/lib/package-manager", () => ({
|
|
28
|
+
usePackageManager: () => packageManagerResult,
|
|
29
|
+
detectPackageManager: (workspaceRootDir: string) =>
|
|
30
|
+
mockDetectPackageManager(workspaceRootDir),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sets up a minimal workspace file structure with a target package and
|
|
35
|
+
* workspace packages so that getInternalPackageNames can resolve them.
|
|
36
|
+
*/
|
|
37
|
+
async function createWorkspace(
|
|
38
|
+
rootDir: string,
|
|
39
|
+
{
|
|
40
|
+
targetDeps = {} as Record<string, string>,
|
|
41
|
+
targetDevDeps = {} as Record<string, string>,
|
|
42
|
+
packages = [] as {
|
|
43
|
+
name: string;
|
|
44
|
+
dir: string;
|
|
45
|
+
deps?: Record<string, string>;
|
|
46
|
+
}[],
|
|
47
|
+
},
|
|
48
|
+
) {
|
|
49
|
+
const packagesDir = path.join(rootDir, "packages");
|
|
50
|
+
const targetDir = path.join(packagesDir, "target");
|
|
51
|
+
|
|
52
|
+
/** Write pnpm-workspace.yaml so the registry can find packages */
|
|
53
|
+
await fs.writeFile(
|
|
54
|
+
path.join(rootDir, "pnpm-workspace.yaml"),
|
|
55
|
+
"packages:\n - packages/*\n",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/** Write a pnpm lockfile so package manager detection works */
|
|
59
|
+
await fs.writeFile(
|
|
60
|
+
path.join(rootDir, "pnpm-lock.yaml"),
|
|
61
|
+
"lockfileVersion: '9.0'\n",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/** Write target package manifest */
|
|
65
|
+
await fs.ensureDir(targetDir);
|
|
66
|
+
await fs.writeJson(path.join(targetDir, "package.json"), {
|
|
67
|
+
name: "@test/target",
|
|
68
|
+
version: "0.0.0",
|
|
69
|
+
dependencies: targetDeps,
|
|
70
|
+
devDependencies: targetDevDeps,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/** Write workspace package manifests */
|
|
74
|
+
for (const pkg of packages) {
|
|
75
|
+
const pkgDir = path.join(packagesDir, pkg.dir);
|
|
76
|
+
await fs.ensureDir(pkgDir);
|
|
77
|
+
await fs.writeJson(path.join(pkgDir, "package.json"), {
|
|
78
|
+
name: pkg.name,
|
|
79
|
+
version: "0.0.0",
|
|
80
|
+
dependencies: pkg.deps ?? {},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return targetDir;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe("getInternalPackageNames", () => {
|
|
88
|
+
let tempDir: string;
|
|
89
|
+
let originalCwd: string;
|
|
90
|
+
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
vi.clearAllMocks();
|
|
93
|
+
originalCwd = process.cwd();
|
|
94
|
+
tempDir = await fs.mkdtemp(
|
|
95
|
+
path.join(os.tmpdir(), "isolate-get-internal-test-"),
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterEach(async () => {
|
|
100
|
+
process.chdir(originalCwd);
|
|
101
|
+
await fs.remove(tempDir);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns internal package names from the target manifest", async () => {
|
|
105
|
+
const targetDir = await createWorkspace(tempDir, {
|
|
106
|
+
targetDeps: { "@test/shared": "0.0.0", lodash: "^4.0.0" },
|
|
107
|
+
packages: [{ name: "@test/shared", dir: "shared" }],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
process.chdir(targetDir);
|
|
111
|
+
|
|
112
|
+
const result = await getInternalPackageNames({ workspaceRoot: "../.." });
|
|
113
|
+
expect(result).toEqual(["@test/shared"]);
|
|
114
|
+
expect(mockDetectPackageManager).toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("excludes devDependencies by default", async () => {
|
|
118
|
+
const targetDir = await createWorkspace(tempDir, {
|
|
119
|
+
targetDeps: { "@test/shared": "0.0.0" },
|
|
120
|
+
targetDevDeps: { "@test/dev-tool": "0.0.0" },
|
|
121
|
+
packages: [
|
|
122
|
+
{ name: "@test/shared", dir: "shared" },
|
|
123
|
+
{ name: "@test/dev-tool", dir: "dev-tool" },
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
process.chdir(targetDir);
|
|
128
|
+
|
|
129
|
+
const result = await getInternalPackageNames({ workspaceRoot: "../.." });
|
|
130
|
+
expect(result).toEqual(["@test/shared"]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("includes devDependencies when configured", async () => {
|
|
134
|
+
const targetDir = await createWorkspace(tempDir, {
|
|
135
|
+
targetDeps: { "@test/shared": "0.0.0" },
|
|
136
|
+
targetDevDeps: { "@test/dev-tool": "0.0.0" },
|
|
137
|
+
packages: [
|
|
138
|
+
{ name: "@test/shared", dir: "shared" },
|
|
139
|
+
{ name: "@test/dev-tool", dir: "dev-tool" },
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
process.chdir(targetDir);
|
|
144
|
+
|
|
145
|
+
const result = await getInternalPackageNames({
|
|
146
|
+
workspaceRoot: "../..",
|
|
147
|
+
includeDevDependencies: true,
|
|
148
|
+
});
|
|
149
|
+
expect(result).toEqual(
|
|
150
|
+
expect.arrayContaining(["@test/shared", "@test/dev-tool"]),
|
|
151
|
+
);
|
|
152
|
+
expect(result).toHaveLength(2);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("resolves recursive internal dependencies", async () => {
|
|
156
|
+
const targetDir = await createWorkspace(tempDir, {
|
|
157
|
+
targetDeps: { "@test/app-utils": "0.0.0" },
|
|
158
|
+
packages: [
|
|
159
|
+
{
|
|
160
|
+
name: "@test/app-utils",
|
|
161
|
+
dir: "app-utils",
|
|
162
|
+
deps: { "@test/core": "0.0.0" },
|
|
163
|
+
},
|
|
164
|
+
{ name: "@test/core", dir: "core" },
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
process.chdir(targetDir);
|
|
169
|
+
|
|
170
|
+
const result = await getInternalPackageNames({ workspaceRoot: "../.." });
|
|
171
|
+
expect(result).toEqual(
|
|
172
|
+
expect.arrayContaining(["@test/app-utils", "@test/core"]),
|
|
173
|
+
);
|
|
174
|
+
expect(result).toHaveLength(2);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("returns an empty array when there are no internal dependencies", async () => {
|
|
178
|
+
const targetDir = await createWorkspace(tempDir, {
|
|
179
|
+
targetDeps: { lodash: "^4.0.0", express: "^4.0.0" },
|
|
180
|
+
packages: [{ name: "@test/unrelated", dir: "unrelated" }],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
process.chdir(targetDir);
|
|
184
|
+
|
|
185
|
+
const result = await getInternalPackageNames({ workspaceRoot: "../.." });
|
|
186
|
+
expect(result).toEqual([]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("loads config from isolate.config.json when no config is passed", async () => {
|
|
190
|
+
const targetDir = await createWorkspace(tempDir, {
|
|
191
|
+
targetDeps: { "@test/shared": "0.0.0" },
|
|
192
|
+
targetDevDeps: { "@test/dev-tool": "0.0.0" },
|
|
193
|
+
packages: [
|
|
194
|
+
{ name: "@test/shared", dir: "shared" },
|
|
195
|
+
{ name: "@test/dev-tool", dir: "dev-tool" },
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/** Write config file with includeDevDependencies enabled */
|
|
200
|
+
await fs.writeJson(path.join(targetDir, "isolate.config.json"), {
|
|
201
|
+
workspaceRoot: "../..",
|
|
202
|
+
includeDevDependencies: true,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
process.chdir(targetDir);
|
|
206
|
+
|
|
207
|
+
const result = await getInternalPackageNames();
|
|
208
|
+
expect(result).toEqual(
|
|
209
|
+
expect.arrayContaining(["@test/shared", "@test/dev-tool"]),
|
|
210
|
+
);
|
|
211
|
+
expect(result).toHaveLength(2);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { IsolateConfig } from "./lib/config";
|
|
3
|
+
import { resolveConfig, resolveWorkspacePaths } from "./lib/config";
|
|
4
|
+
import { detectPackageManager } from "./lib/package-manager";
|
|
5
|
+
import { createPackagesRegistry, listInternalPackages } from "./lib/registry";
|
|
6
|
+
import type { PackageManifest } from "./lib/types";
|
|
7
|
+
import { readTypedJson } from "./lib/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the names of all internal workspace packages that the target package
|
|
11
|
+
* depends on. This is useful for tools like tsup that need a list of internal
|
|
12
|
+
* packages to include in `noExternal`.
|
|
13
|
+
*
|
|
14
|
+
* If no config is passed, it reads from `isolate.config.{ts,js,json}` in the
|
|
15
|
+
* current working directory.
|
|
16
|
+
*/
|
|
17
|
+
export async function getInternalPackageNames(
|
|
18
|
+
config?: IsolateConfig,
|
|
19
|
+
): Promise<string[]> {
|
|
20
|
+
const resolvedConfig = resolveConfig(config);
|
|
21
|
+
const { targetPackageDir, workspaceRootDir } =
|
|
22
|
+
resolveWorkspacePaths(resolvedConfig);
|
|
23
|
+
|
|
24
|
+
detectPackageManager(workspaceRootDir);
|
|
25
|
+
|
|
26
|
+
const targetPackageManifest = await readTypedJson<PackageManifest>(
|
|
27
|
+
path.join(targetPackageDir, "package.json"),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const packagesRegistry = await createPackagesRegistry(
|
|
31
|
+
workspaceRootDir,
|
|
32
|
+
resolvedConfig.workspacePackages,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return listInternalPackages(targetPackageManifest, packagesRegistry, {
|
|
36
|
+
includeDevDependencies: resolvedConfig.includeDevDependencies,
|
|
37
|
+
});
|
|
38
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { isolate } from "./isolate";
|
|
2
1
|
export { isolate } from "./isolate";
|
|
2
|
+
export { getInternalPackageNames } from "./get-internal-package-names";
|
|
3
|
+
export { defineConfig } from "./lib/config";
|
|
4
|
+
export type { IsolateConfig } from "./lib/config";
|
|
3
5
|
export type { Logger } from "./lib/logger";
|
|
4
|
-
|
|
5
|
-
export type IsolateExports = {
|
|
6
|
-
isolate: typeof isolate;
|
|
7
|
-
};
|
package/src/isolate-bin.ts
CHANGED
package/src/isolate.ts
CHANGED
|
@@ -4,7 +4,7 @@ import assert from "node:assert";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { unique } from "remeda";
|
|
6
6
|
import type { IsolateConfig } from "./lib/config";
|
|
7
|
-
import { resolveConfig } from "./lib/config";
|
|
7
|
+
import { resolveConfig, resolveWorkspacePaths } from "./lib/config";
|
|
8
8
|
import { processLockfile } from "./lib/lockfile";
|
|
9
9
|
import { setLogLevel, useLogger } from "./lib/logger";
|
|
10
10
|
import {
|
|
@@ -44,23 +44,13 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
44
44
|
const log = useLogger();
|
|
45
45
|
|
|
46
46
|
const { version: libraryVersion } = await readTypedJson<PackageManifest>(
|
|
47
|
-
path.join(path.join(__dirname, "..", "package.json"))
|
|
47
|
+
path.join(path.join(__dirname, "..", "package.json")),
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
log.debug("Using isolate-package version", libraryVersion);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* root of the workspace. If targetPackagePath is undefined (the default),
|
|
55
|
-
* we assume that the configuration lives in the target package directory.
|
|
56
|
-
*/
|
|
57
|
-
const targetPackageDir = config.targetPackagePath
|
|
58
|
-
? path.join(process.cwd(), config.targetPackagePath)
|
|
59
|
-
: process.cwd();
|
|
60
|
-
|
|
61
|
-
const workspaceRootDir = config.targetPackagePath
|
|
62
|
-
? process.cwd()
|
|
63
|
-
: path.join(targetPackageDir, config.workspaceRoot);
|
|
52
|
+
const { targetPackageDir, workspaceRootDir } =
|
|
53
|
+
resolveWorkspacePaths(config);
|
|
64
54
|
|
|
65
55
|
const buildOutputDir = await getBuildOutputDir({
|
|
66
56
|
targetPackageDir,
|
|
@@ -70,20 +60,20 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
70
60
|
|
|
71
61
|
assert(
|
|
72
62
|
fs.existsSync(buildOutputDir),
|
|
73
|
-
`Failed to find build output path at ${buildOutputDir}. Please make sure you build the source before isolating it
|
|
63
|
+
`Failed to find build output path at ${buildOutputDir}. Please make sure you build the source before isolating it.`,
|
|
74
64
|
);
|
|
75
65
|
|
|
76
66
|
log.debug("Workspace root resolved to", workspaceRootDir);
|
|
77
67
|
log.debug(
|
|
78
68
|
"Isolate target package",
|
|
79
|
-
getRootRelativeLogPath(targetPackageDir, workspaceRootDir)
|
|
69
|
+
getRootRelativeLogPath(targetPackageDir, workspaceRootDir),
|
|
80
70
|
);
|
|
81
71
|
|
|
82
72
|
const isolateDir = path.join(targetPackageDir, config.isolateDirName);
|
|
83
73
|
|
|
84
74
|
log.debug(
|
|
85
75
|
"Isolate output directory",
|
|
86
|
-
getRootRelativeLogPath(isolateDir, workspaceRootDir)
|
|
76
|
+
getRootRelativeLogPath(isolateDir, workspaceRootDir),
|
|
87
77
|
);
|
|
88
78
|
|
|
89
79
|
if (fs.existsSync(isolateDir)) {
|
|
@@ -97,13 +87,13 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
97
87
|
await fs.ensureDir(tmpDir);
|
|
98
88
|
|
|
99
89
|
const targetPackageManifest = await readTypedJson<PackageManifest>(
|
|
100
|
-
path.join(targetPackageDir, "package.json")
|
|
90
|
+
path.join(targetPackageDir, "package.json"),
|
|
101
91
|
);
|
|
102
92
|
|
|
103
93
|
/** Validate mandatory fields for the target package */
|
|
104
94
|
validateManifestMandatoryFields(
|
|
105
95
|
targetPackageManifest,
|
|
106
|
-
getRootRelativeLogPath(targetPackageDir, workspaceRootDir)
|
|
96
|
+
getRootRelativeLogPath(targetPackageDir, workspaceRootDir),
|
|
107
97
|
);
|
|
108
98
|
|
|
109
99
|
const packageManager = detectPackageManager(workspaceRootDir);
|
|
@@ -111,7 +101,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
111
101
|
log.debug(
|
|
112
102
|
"Detected package manager",
|
|
113
103
|
packageManager.name,
|
|
114
|
-
packageManager.version
|
|
104
|
+
packageManager.version,
|
|
115
105
|
);
|
|
116
106
|
|
|
117
107
|
if (shouldUsePnpmPack()) {
|
|
@@ -124,7 +114,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
124
114
|
*/
|
|
125
115
|
const packagesRegistry = await createPackagesRegistry(
|
|
126
116
|
workspaceRootDir,
|
|
127
|
-
config.workspacePackages
|
|
117
|
+
config.workspacePackages,
|
|
128
118
|
);
|
|
129
119
|
|
|
130
120
|
const internalPackageNames = listInternalPackages(
|
|
@@ -132,7 +122,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
132
122
|
packagesRegistry,
|
|
133
123
|
{
|
|
134
124
|
includeDevDependencies: config.includeDevDependencies,
|
|
135
|
-
}
|
|
125
|
+
},
|
|
136
126
|
);
|
|
137
127
|
|
|
138
128
|
/**
|
|
@@ -144,7 +134,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
144
134
|
packagesRegistry,
|
|
145
135
|
{
|
|
146
136
|
includeDevDependencies: false,
|
|
147
|
-
}
|
|
137
|
+
},
|
|
148
138
|
);
|
|
149
139
|
|
|
150
140
|
/** Validate mandatory fields for all internal packages that will be isolated */
|
|
@@ -155,7 +145,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
155
145
|
validateManifestMandatoryFields(
|
|
156
146
|
packageDef.manifest,
|
|
157
147
|
getRootRelativeLogPath(packageDef.absoluteDir, workspaceRootDir),
|
|
158
|
-
isProductionDependency
|
|
148
|
+
isProductionDependency,
|
|
159
149
|
);
|
|
160
150
|
}
|
|
161
151
|
|
|
@@ -169,7 +159,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
169
159
|
packedFilesByName,
|
|
170
160
|
packagesRegistry,
|
|
171
161
|
tmpDir,
|
|
172
|
-
isolateDir
|
|
162
|
+
isolateDir,
|
|
173
163
|
);
|
|
174
164
|
|
|
175
165
|
/** Adapt the manifest files for all the unpacked local dependencies */
|
|
@@ -250,10 +240,10 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
250
240
|
Object.entries(copiedPatches).map(([spec, patchFile]) => [
|
|
251
241
|
spec,
|
|
252
242
|
patchFile.path,
|
|
253
|
-
])
|
|
243
|
+
]),
|
|
254
244
|
);
|
|
255
245
|
log.debug(
|
|
256
|
-
`Added ${Object.keys(copiedPatches).length} patches to isolated package.json
|
|
246
|
+
`Added ${Object.keys(copiedPatches).length} patches to isolated package.json`,
|
|
257
247
|
);
|
|
258
248
|
}
|
|
259
249
|
|
|
@@ -281,8 +271,8 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
281
271
|
const packagesFolderNames = unique(
|
|
282
272
|
internalPackageNames.map(
|
|
283
273
|
(name) =>
|
|
284
|
-
path.parse(got(packagesRegistry, name).rootRelativeDir).dir
|
|
285
|
-
)
|
|
274
|
+
path.parse(got(packagesRegistry, name).rootRelativeDir).dir,
|
|
275
|
+
),
|
|
286
276
|
);
|
|
287
277
|
|
|
288
278
|
log.debug("Generating pnpm-workspace.yaml for Rush workspace");
|
|
@@ -296,7 +286,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
296
286
|
} else {
|
|
297
287
|
fs.copyFileSync(
|
|
298
288
|
path.join(workspaceRootDir, "pnpm-workspace.yaml"),
|
|
299
|
-
path.join(isolateDir, "pnpm-workspace.yaml")
|
|
289
|
+
path.join(isolateDir, "pnpm-workspace.yaml"),
|
|
300
290
|
);
|
|
301
291
|
}
|
|
302
292
|
}
|
|
@@ -321,7 +311,7 @@ export function createIsolator(config?: IsolateConfig) {
|
|
|
321
311
|
*/
|
|
322
312
|
log.debug(
|
|
323
313
|
"Deleting temp directory",
|
|
324
|
-
getRootRelativeLogPath(tmpDir, workspaceRootDir)
|
|
314
|
+
getRootRelativeLogPath(tmpDir, workspaceRootDir),
|
|
325
315
|
);
|
|
326
316
|
await fs.remove(tmpDir);
|
|
327
317
|
|
package/src/lib/cli.test.ts
CHANGED
|
@@ -36,7 +36,7 @@ describe("wasFlagExplicitlyPassed", () => {
|
|
|
36
36
|
it("detects a short flag", () => {
|
|
37
37
|
const argv = ["node", "isolate", "-d"];
|
|
38
38
|
expect(wasFlagExplicitlyPassed("includeDevDependencies", argv, "d")).toBe(
|
|
39
|
-
true
|
|
39
|
+
true,
|
|
40
40
|
);
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -70,7 +70,7 @@ describe("parseLogLevel", () => {
|
|
|
70
70
|
|
|
71
71
|
it("throws for an invalid log level", () => {
|
|
72
72
|
expect(() => parseLogLevel("verbose")).toThrow(
|
|
73
|
-
'Invalid log level: "verbose"'
|
|
73
|
+
'Invalid log level: "verbose"',
|
|
74
74
|
);
|
|
75
75
|
});
|
|
76
76
|
});
|
|
@@ -168,7 +168,7 @@ describe("buildCliOverrides", () => {
|
|
|
168
168
|
const flags: ParsedFlags = { ...defaultFlags, logLevel: "verbose" };
|
|
169
169
|
const argv = ["node", "isolate", "--log-level", "verbose"];
|
|
170
170
|
expect(() => buildCliOverrides(flags, argv)).toThrow(
|
|
171
|
-
'Invalid log level: "verbose"'
|
|
171
|
+
'Invalid log level: "verbose"',
|
|
172
172
|
);
|
|
173
173
|
});
|
|
174
174
|
|
package/src/lib/cli.ts
CHANGED
|
@@ -12,14 +12,14 @@ const validLogLevels: readonly LogLevel[] = ["info", "debug", "warn", "error"];
|
|
|
12
12
|
export function wasFlagExplicitlyPassed(
|
|
13
13
|
flagName: string,
|
|
14
14
|
argv: string[],
|
|
15
|
-
shortFlag?: string
|
|
15
|
+
shortFlag?: string,
|
|
16
16
|
): boolean {
|
|
17
17
|
const kebab = flagName.replace(/[A-Z]/g, (l) => `-${l.toLowerCase()}`);
|
|
18
18
|
return argv.some(
|
|
19
19
|
(arg) =>
|
|
20
20
|
arg === `--${kebab}` ||
|
|
21
21
|
arg === `--no-${kebab}` ||
|
|
22
|
-
(shortFlag !== undefined && arg === `-${shortFlag}`)
|
|
22
|
+
(shortFlag !== undefined && arg === `-${shortFlag}`),
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -34,7 +34,7 @@ export function parseLogLevel(value: string | undefined): LogLevel | undefined {
|
|
|
34
34
|
|
|
35
35
|
if (!validLogLevels.includes(value as LogLevel)) {
|
|
36
36
|
throw new Error(
|
|
37
|
-
`Invalid log level: "${value}". Must be one of: ${validLogLevels.join(", ")}
|
|
37
|
+
`Invalid log level: "${value}". Must be one of: ${validLogLevels.join(", ")}`,
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -64,7 +64,7 @@ export type ParsedFlags = {
|
|
|
64
64
|
*/
|
|
65
65
|
export function buildCliOverrides(
|
|
66
66
|
flags: ParsedFlags,
|
|
67
|
-
argv: string[]
|
|
67
|
+
argv: string[],
|
|
68
68
|
): IsolateConfig {
|
|
69
69
|
const logLevel = parseLogLevel(flags.logLevel);
|
|
70
70
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { defineConfig, loadConfigFromFile } from "./config";
|
|
6
|
+
|
|
7
|
+
/** Shared mock logger instance so assertions can check calls. */
|
|
8
|
+
const mockLogger = {
|
|
9
|
+
debug: vi.fn(),
|
|
10
|
+
info: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
error: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
vi.mock("~/lib/logger", () => ({
|
|
16
|
+
useLogger: () => mockLogger,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe("loadConfigFromFile", () => {
|
|
20
|
+
let tempDir: string;
|
|
21
|
+
let originalCwd: string;
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
originalCwd = process.cwd();
|
|
26
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "isolate-config-test-"));
|
|
27
|
+
process.chdir(tempDir);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
process.chdir(originalCwd);
|
|
32
|
+
await fs.remove(tempDir);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("returns empty object when no config file exists", () => {
|
|
36
|
+
const config = loadConfigFromFile();
|
|
37
|
+
expect(config).toEqual({});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("loads a JSON config file", async () => {
|
|
41
|
+
await fs.writeJson(path.join(tempDir, "isolate.config.json"), {
|
|
42
|
+
isolateDirName: "output",
|
|
43
|
+
workspaceRoot: "../../..",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const config = loadConfigFromFile();
|
|
47
|
+
expect(config).toEqual({
|
|
48
|
+
isolateDirName: "output",
|
|
49
|
+
workspaceRoot: "../../..",
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("loads a TypeScript config file", async () => {
|
|
54
|
+
await fs.writeFile(
|
|
55
|
+
path.join(tempDir, "isolate.config.ts"),
|
|
56
|
+
`export default { isolateDirName: "from-ts", workspaceRoot: "../.." };`,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const config = loadConfigFromFile();
|
|
60
|
+
expect(config).toEqual({
|
|
61
|
+
isolateDirName: "from-ts",
|
|
62
|
+
workspaceRoot: "../..",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("loads a TypeScript config file that uses defineConfig", async () => {
|
|
67
|
+
/**
|
|
68
|
+
* The subprocess can't import from "isolate-package" since it's not
|
|
69
|
+
* installed in the temp dir, so we inline the defineConfig identity
|
|
70
|
+
* function to verify the pattern works end-to-end.
|
|
71
|
+
*/
|
|
72
|
+
await fs.writeFile(
|
|
73
|
+
path.join(tempDir, "isolate.config.ts"),
|
|
74
|
+
[
|
|
75
|
+
`const defineConfig = (c: Record<string, unknown>) => c;`,
|
|
76
|
+
`export default defineConfig({ isolateDirName: "defined" });`,
|
|
77
|
+
].join("\n"),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const config = loadConfigFromFile();
|
|
81
|
+
expect(config).toEqual({ isolateDirName: "defined" });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("loads a JavaScript config file", async () => {
|
|
85
|
+
await fs.writeFile(
|
|
86
|
+
path.join(tempDir, "isolate.config.js"),
|
|
87
|
+
`export default { isolateDirName: "from-js", workspaceRoot: "../.." };`,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const config = loadConfigFromFile();
|
|
91
|
+
expect(config).toEqual({
|
|
92
|
+
isolateDirName: "from-js",
|
|
93
|
+
workspaceRoot: "../..",
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("prefers TypeScript config and warns when multiple exist", async () => {
|
|
98
|
+
await fs.writeJson(path.join(tempDir, "isolate.config.json"), {
|
|
99
|
+
isolateDirName: "from-json",
|
|
100
|
+
});
|
|
101
|
+
await fs.writeFile(
|
|
102
|
+
path.join(tempDir, "isolate.config.ts"),
|
|
103
|
+
`export default { isolateDirName: "from-ts" };`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const config = loadConfigFromFile();
|
|
107
|
+
expect(config).toEqual({ isolateDirName: "from-ts" });
|
|
108
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
109
|
+
expect.stringContaining("Found multiple config files"),
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("prefers JavaScript config over JSON", async () => {
|
|
114
|
+
await fs.writeJson(path.join(tempDir, "isolate.config.json"), {
|
|
115
|
+
isolateDirName: "from-json",
|
|
116
|
+
});
|
|
117
|
+
await fs.writeFile(
|
|
118
|
+
path.join(tempDir, "isolate.config.js"),
|
|
119
|
+
`export default { isolateDirName: "from-js" };`,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const config = loadConfigFromFile();
|
|
123
|
+
expect(config).toEqual({ isolateDirName: "from-js" });
|
|
124
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
125
|
+
expect.stringContaining("Found multiple config files"),
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("throws when the config file has no default export", async () => {
|
|
130
|
+
await fs.writeFile(
|
|
131
|
+
path.join(tempDir, "isolate.config.ts"),
|
|
132
|
+
`export const config = { isolateDirName: "oops" };`,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(() => loadConfigFromFile()).toThrow("Failed to load config from");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("throws when the default export is not an object", async () => {
|
|
139
|
+
await fs.writeFile(
|
|
140
|
+
path.join(tempDir, "isolate.config.ts"),
|
|
141
|
+
`export default "not an object";`,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(() => loadConfigFromFile()).toThrow("Failed to load config from");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("throws when the TypeScript file has a syntax error", async () => {
|
|
148
|
+
await fs.writeFile(
|
|
149
|
+
path.join(tempDir, "isolate.config.ts"),
|
|
150
|
+
`export default {{{`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(() => loadConfigFromFile()).toThrow("Failed to load config from");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("defineConfig", () => {
|
|
158
|
+
it("returns the config object unchanged", () => {
|
|
159
|
+
const input = { isolateDirName: "output", workspaceRoot: "../.." };
|
|
160
|
+
const result = defineConfig(input);
|
|
161
|
+
expect(result).toBe(input);
|
|
162
|
+
});
|
|
163
|
+
});
|