isolate-package 1.29.0-1 → 1.29.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.
@@ -4,6 +4,16 @@ import path from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { getInternalPackageNames } from "./get-internal-package-names";
6
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
+
7
17
  const packageManagerResult = {
8
18
  name: "pnpm",
9
19
  version: "9.0.0",
package/src/isolate.ts CHANGED
@@ -193,12 +193,11 @@ export function createIsolator(config?: IsolateConfig) {
193
193
 
194
194
  /**
195
195
  * Copy patch files before generating lockfile so the lockfile contains the
196
- * correct paths. Only copy patches when output uses pnpm or bun, since
197
- * patched dependencies are stored in their lockfiles.
196
+ * correct paths. Only copy patches when output uses pnpm, since patched
197
+ * dependencies are a pnpm-specific feature.
198
198
  */
199
199
  const shouldCopyPatches =
200
- (packageManager.name === "pnpm" || packageManager.name === "bun") &&
201
- !config.forceNpm;
200
+ packageManager.name === "pnpm" && !config.forceNpm;
202
201
 
203
202
  const copiedPatches = shouldCopyPatches
204
203
  ? await copyPatches({
@@ -292,18 +291,6 @@ export function createIsolator(config?: IsolateConfig) {
292
291
  }
293
292
  }
294
293
 
295
- if (packageManager.name === "bun" && !config.forceNpm) {
296
- /** Add workspaces field to the manifest so Bun treats the isolate as a workspace */
297
- const manifest = await readManifest(isolateDir);
298
- const workspaceGlobs = unique(
299
- internalPackageNames.map(
300
- (name) => path.parse(got(packagesRegistry, name).rootRelativeDir).dir,
301
- ),
302
- ).map((x) => path.join(x, "/*"));
303
- manifest.workspaces = workspaceGlobs;
304
- await writeManifest(isolateDir, manifest);
305
- }
306
-
307
294
  /**
308
295
  * If there is an .npmrc file in the workspace root, copy it to the isolate
309
296
  * because the settings there could affect how the lockfile is resolved.
@@ -318,15 +305,6 @@ export function createIsolator(config?: IsolateConfig) {
318
305
  log.debug("Copied .npmrc file to the isolate output");
319
306
  }
320
307
 
321
- if (packageManager.name === "bun" && !config.forceNpm) {
322
- const bunfigPath = path.join(workspaceRootDir, "bunfig.toml");
323
-
324
- if (fs.existsSync(bunfigPath)) {
325
- fs.copyFileSync(bunfigPath, path.join(isolateDir, "bunfig.toml"));
326
- log.debug("Copied bunfig.toml file to the isolate output");
327
- }
328
- }
329
-
330
308
  /**
331
309
  * Clean up. Only do this when things succeed, so we can look at the temp
332
310
  * folder in case something goes wrong.
@@ -1,4 +1,3 @@
1
- export * from "./generate-bun-lockfile";
2
1
  export * from "./generate-npm-lockfile";
3
2
  export * from "./generate-pnpm-lockfile";
4
3
  export * from "./generate-yarn-lockfile";
@@ -3,7 +3,6 @@ import { useLogger } from "../logger";
3
3
  import { usePackageManager } from "../package-manager";
4
4
  import type { PackageManifest, PackagesRegistry, PatchFile } from "../types";
5
5
  import {
6
- generateBunLockfile,
7
6
  generateNpmLockfile,
8
7
  generatePnpmLockfile,
9
8
  generateYarnLockfile,
@@ -98,14 +97,15 @@ export async function processLockfile({
98
97
  break;
99
98
  }
100
99
  case "bun": {
101
- await generateBunLockfile({
100
+ log.warn(
101
+ `Ouput lockfiles for Bun are not yet supported. Using NPM for output`,
102
+ );
103
+ await generateNpmLockfile({
102
104
  workspaceRootDir,
103
- targetPackageDir,
104
105
  isolateDir,
105
- internalDepPackageNames,
106
- packagesRegistry,
107
- includeDevDependencies: config.includeDevDependencies,
108
106
  });
107
+
108
+ usedFallbackToNpm = true;
109
109
  break;
110
110
  }
111
111
  default:
@@ -51,12 +51,11 @@ export async function adaptTargetPackageManifest({
51
51
  };
52
52
 
53
53
  const adaptedManifest =
54
- (packageManager.name === "pnpm" || packageManager.name === "bun") &&
55
- !forceNpm
54
+ packageManager.name === "pnpm" && !forceNpm
56
55
  ? /**
57
- * For PNPM and Bun the output itself is a workspace so we can preserve
58
- * the specifiers with "workspace:*" in the output manifest, but we do
59
- * want to adopt the pnpm.overrides field from the root package.json.
56
+ * For PNPM the output itself is a workspace so we can preserve the specifiers
57
+ * with "workspace:*" in the output manifest, but we do want to adopt the
58
+ * pnpm.overrides field from the root package.json.
60
59
  */
61
60
  await adoptPnpmFieldsFromRoot(
62
61
  manifestWithResolvedCatalogs,
@@ -0,0 +1,132 @@
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 devDependencies from internal dependency manifests", async () => {
99
+ const manifest: PackageManifest = {
100
+ name: "@repo/shared",
101
+ version: "1.0.0",
102
+ dependencies: {
103
+ lodash: "^4.0.0",
104
+ },
105
+ devDependencies: {
106
+ vitest: "^1.0.0",
107
+ typescript: "^5.0.0",
108
+ },
109
+ };
110
+
111
+ const packagesRegistry = createRegistry({
112
+ "@repo/shared": {
113
+ rootRelativeDir: "packages/shared",
114
+ manifest,
115
+ },
116
+ });
117
+
118
+ await adaptInternalPackageManifests({
119
+ internalPackageNames: ["@repo/shared"],
120
+ packagesRegistry,
121
+ isolateDir: "/output",
122
+ forceNpm: false,
123
+ workspaceRootDir: "/workspace",
124
+ });
125
+
126
+ expect(writeManifest).toHaveBeenCalledOnce();
127
+
128
+ const writtenManifest = writeManifest.mock.calls[0]![1];
129
+
130
+ expect(writtenManifest.devDependencies).toBeUndefined();
131
+ });
132
+ });
@@ -31,8 +31,8 @@ export async function adaptInternalPackageManifests({
31
31
  internalPackageNames.map(async (packageName) => {
32
32
  const { manifest, rootRelativeDir } = got(packagesRegistry, packageName);
33
33
 
34
- /** Dev dependencies and scripts are never included for internal deps */
35
- const strippedManifest = omit(manifest, ["scripts", "devDependencies"]);
34
+ /** Dev dependencies are never included for internal deps */
35
+ const strippedManifest = omit(manifest, ["devDependencies"]);
36
36
 
37
37
  /** Resolve catalog dependencies before adapting internal deps */
38
38
  const manifestWithResolvedCatalogs = {
@@ -44,11 +44,10 @@ export async function adaptInternalPackageManifests({
44
44
  };
45
45
 
46
46
  const outputManifest =
47
- (packageManager.name === "pnpm" || packageManager.name === "bun") &&
48
- !forceNpm
47
+ packageManager.name === "pnpm" && !forceNpm
49
48
  ? /**
50
- * For PNPM and Bun the output itself is a workspace so we can preserve
51
- * the specifiers with "workspace:*" in the output manifest.
49
+ * For PNPM the output itself is a workspace so we can preserve the specifiers
50
+ * with "workspace:*" in the output manifest.
52
51
  */
53
52
  manifestWithResolvedCatalogs
54
53
  : /** For other package managers we replace the links to internal dependencies */
@@ -43,7 +43,7 @@ describe("validateManifestMandatoryFields", () => {
43
43
 
44
44
  expect(() =>
45
45
  validateManifestMandatoryFields(invalidDevManifest, packagePath, false),
46
- ).toThrow(/missing mandatory fields: version/);
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 mandatory fields: version/);
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 mandatory fields: files/);
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 mandatory fields: files/);
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 mandatory fields: files/);
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 helpful error message", () => {
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(/missing mandatory fields: version, files/);
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(invalidManifest, packagePath),
116
- ).toThrow(/See the documentation for more details/);
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 errorMessage = `Package at ${packagePath} is missing mandatory fields: ${missingFields.join(", ")}. See the documentation for more details.`;
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);