bun-nested-workspace 0.1.0 → 0.2.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/README.md CHANGED
@@ -4,15 +4,16 @@ Embed a checkout of another Bun workspace repository into a subdirectory of your
4
4
 
5
5
  The nested repository stays a self-contained project with its own root `package.json` and `bun.lock`.
6
6
  Its packages, such as `nested/apps/*` and `nested/packages/*`, become workspaces of the host, while its root does not.
7
+ The host installs none of the dependencies of the nested root `package.json` and cannot run its scripts.
7
8
  Bun reads catalogs, overrides, patches, and trusted dependencies from the workspace root only, and resolves the whole workspace from a single lockfile.
8
9
  This package carries those root fields over to the host and merges the nested lockfile into the host lockfile, so the nested packages build in the host with the same dependency versions as on their own.
9
10
 
10
11
  ## Install
11
12
 
12
- The CLI runs before the host dependencies are installed, so run it with `bunx` and pin the version:
13
+ The CLI runs before the host dependencies are installed, so run it with `bunx`:
13
14
 
14
15
  ```sh
15
- bunx bun-nested-workspace@0.1.0
16
+ bunx bun-nested-workspace
16
17
  ```
17
18
 
18
19
  It requires Bun 1.2.19 or later, the first version that resolves `catalog:` dependencies from both catalog locations.
@@ -43,7 +44,7 @@ The config takes these fields:
43
44
  Run the CLI in the host root after switching the nested checkout to another commit:
44
45
 
45
46
  ```sh
46
- bunx bun-nested-workspace@0.1.0
47
+ bunx bun-nested-workspace
47
48
  ```
48
49
 
49
50
  The sync:
@@ -55,22 +56,33 @@ The sync:
55
56
 
56
57
  Commit `package.json` and `bun.lock` after the sync.
57
58
 
58
- In a Docker build, run the sync with `--lockfile-only` in a stage that contains only the dependency files, before installing dependencies:
59
+ In a Docker build, run the sync with `--lockfile-only` in a stage that contains only the dependency files, before installing dependencies.
60
+ `bun install` also needs the patch files and the install configuration, such as `bunfig.toml` and `.npmrc`, so keep them, along with any files they reference.
61
+ A separate stage can strip the build context down to these files, so the Dockerfile does not list every workspace package:
59
62
 
60
63
  ```dockerfile
61
- COPY package.json bun.lock ./
62
- COPY nested/package.json nested/bun.lock nested/
63
- COPY nested/packages/core/package.json nested/packages/core/
64
- RUN bunx bun-nested-workspace@0.1.0 --lockfile-only
64
+ FROM oven/bun:1 AS manifests
65
+ WORKDIR /app
66
+ COPY . .
67
+ RUN find . -type f ! -name package.json ! -name bun.lock ! -name '*.patch' ! -name bunfig.toml ! -name .npmrc -delete
68
+
69
+ FROM oven/bun:1 AS deps
70
+ WORKDIR /app
71
+ COPY --from=manifests /app ./
72
+ RUN bunx bun-nested-workspace --lockfile-only
65
73
  ```
66
74
 
75
+ Exclude `node_modules` and other large directories from the build context with `.dockerignore`.
76
+
67
77
  ### Root fields
68
78
 
69
79
  - `catalog` and `catalogs` each stay where the host defines them; a field the host lacks goes next to the other catalog field, or into `workspaces`.
70
80
  An array `workspaces` field becomes an object when a catalog needs it.
71
81
  - `overrides` and `trustedDependencies` are merged by package name.
82
+ - `resolutions` on either side is an error while `overrides` are synced: Bun ignores `resolutions` once `overrides` is defined, so move its entries to `overrides`.
72
83
  - `trustedDependencies` stays in the host `package.json`, even empty, when either side defines it.
73
84
  An explicit list replaces the default trusted list of Bun, so when only one side defines the field, the packages of the other side lose the default list; list the ones whose lifecycle scripts they need.
85
+ The sync warns when it adds the field to the host.
74
86
  - `patchedDependencies` paths are rewritten through the nested directory, such as `nested/patches/exceljs@4.4.0.patch`.
75
87
 
76
88
  A nested entry replaces a host entry with the same name, while the other host entries stay.
@@ -99,7 +111,7 @@ Only `bun.lock` with `lockfileVersion` 1 or 2 is supported, because the lockfile
99
111
  ## Check
100
112
 
101
113
  ```sh
102
- bunx bun-nested-workspace@0.1.0 --check
114
+ bunx bun-nested-workspace --check
103
115
  ```
104
116
 
105
117
  The check writes nothing, does not run `bun install`, and exits with code 1 when the host files lag behind the nested checkout.
@@ -145,5 +157,6 @@ if (!isInSync(check)) {
145
157
  ```
146
158
 
147
159
  `syncNestedWorkspace` writes the files and runs `bun install` before it checks the result, so a failed sync leaves the written files for inspection.
160
+ Its `onManifestSync` option receives the replaced host entries and whether the sync added `trustedDependencies`, as soon as `package.json` is written and even when `bun install` then fails.
148
161
 
149
162
  Lower-level functions work on parsed files without touching the disk: `syncManifest`, `diffManifests`, `parseLockfile`, `relocateLockfile`, `applyManifestToLockfile`, `mergeLockfiles`, and the `find*` checks.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { a as syncNestedWorkspace, i as loadNestedWorkspace, n as checkNestedWorkspace, r as isInSync, t as BunInstallError, y as getErrorMessage } from "./workspace-DJ2dh3q7.mjs";
2
+ import { a as syncNestedWorkspace, i as loadNestedWorkspace, n as checkNestedWorkspace, r as isInSync, t as BunInstallError, y as getErrorMessage } from "./workspace-BlJ1qGiO.mjs";
3
3
  // src/cli.ts
4
4
  const usage = `Usage:
5
5
  bun-nested-workspace [bun install options] Sync package.json and bun.lock, then run bun install
@@ -43,8 +43,10 @@ async function runCheck(root) {
43
43
  return 1;
44
44
  }
45
45
  async function runSync(root, installArgs) {
46
- const { overridden, ownDependencyChanges, check } = await syncNestedWorkspace(root, { installArgs });
47
- if (overridden.length) console.log(["Nested entries replaced host entries:", ...overridden.map(formatOverridden)].join("\n"));
46
+ const { ownDependencyChanges, check } = await syncNestedWorkspace(root, {
47
+ installArgs,
48
+ onManifestSync: printManifestSync
49
+ });
48
50
  let exitCode = 0;
49
51
  if (ownDependencyChanges.length) {
50
52
  console.error([
@@ -61,6 +63,14 @@ async function runSync(root, installArgs) {
61
63
  }
62
64
  return exitCode;
63
65
  }
66
+ function printManifestSync({ overridden, addedTrustedDependencies }) {
67
+ if (overridden.length) console.log(["Nested entries replaced host entries:", ...overridden.map(formatOverridden)].join("\n"));
68
+ if (addedTrustedDependencies) console.warn([
69
+ "The sync added trustedDependencies to the host package.json.",
70
+ "Bun no longer applies its default trusted list, so host dependencies that relied on it no longer run their lifecycle scripts.",
71
+ "Add the ones the host needs to trustedDependencies."
72
+ ].join("\n"));
73
+ }
64
74
  /** Formats the differences of a failed check as indented lines grouped by kind. */
65
75
  function formatCheckResult(result) {
66
76
  const section = (title, lines) => lines.length ? [` ${title}:`, ...lines.map((line) => ` ${line}`)] : [];
package/dist/index.d.mts CHANGED
@@ -96,6 +96,7 @@ interface PackageManifest {
96
96
  catalog?: Catalog;
97
97
  catalogs?: Record<string, Catalog>;
98
98
  overrides?: Record<string, string>;
99
+ resolutions?: Record<string, string>;
99
100
  patchedDependencies?: Record<string, string>;
100
101
  trustedDependencies?: string[];
101
102
  [configField]?: NestedWorkspaceConfig;
@@ -120,13 +121,21 @@ interface OverriddenEntry {
120
121
  interface ManifestSync {
121
122
  manifest: PackageManifest;
122
123
  overridden: OverriddenEntry[];
124
+ /**
125
+ * Whether the sync added `trustedDependencies` to a host that did not define it.
126
+ *
127
+ * The explicit list replaces the default trusted list of Bun,
128
+ * so host dependencies that relied on the default list no longer run their lifecycle scripts.
129
+ */
130
+ addedTrustedDependencies: boolean;
123
131
  }
124
132
  /**
125
133
  * Returns the host root `package.json` with the configured root fields of the nested `package.json` merged in.
126
134
  *
127
135
  * A nested entry replaces a host entry with the same name, while the other host entries stay.
128
136
  * Entries synced earlier and dropped by the nested repository are removed.
129
- * Throws when the host and the nested repository patch the same package version.
137
+ * Throws when the host and the nested repository patch the same package version,
138
+ * and when either side defines `resolutions` while `overrides` are synced.
130
139
  */
131
140
  export declare function syncManifest(host: PackageManifest, nested: PackageManifest): ManifestSync;
132
141
  /** Difference between the expected and the actual value at a `package.json` path. */
@@ -271,10 +280,15 @@ export declare class BunInstallError extends Error {
271
280
  interface SyncOptions {
272
281
  /** Arguments passed to `bun install`, such as `--lockfile-only`. */
273
282
  installArgs?: readonly string[];
283
+ /**
284
+ * Called with the result of `syncManifest` once the host `package.json` is written, before `bun install` runs.
285
+ *
286
+ * It reports the changes of `package.json` even when `bun install` fails and the sync throws.
287
+ */
288
+ onManifestSync?: (sync: ManifestSync) => void;
274
289
  }
275
290
  /** Result of `syncNestedWorkspace`. */
276
291
  interface SyncResult {
277
- overridden: OverriddenEntry[];
278
292
  /** Own dependencies of host workspaces that the sync re-resolved; the sync failed when this is not empty. */
279
293
  ownDependencyChanges: DependencyMismatch[];
280
294
  /** Check of the synced files; the sync failed when it is not in sync. */
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export { t as BunInstallError, d as applyManifestToLockfile, n as checkNestedWorkspace, g as diffManifests, o as findDirectDependencyMismatches, s as findOwnDependencyChanges, c as findTransitiveMismatches, l as findUncoveredWorkspaces, u as findWorkspaceMismatches, r as isInSync, i as loadNestedWorkspace, f as mergeLockfiles, p as parseLockfile, m as relocateLockfile, h as supportedLockfileVersions, _ as syncManifest, a as syncNestedWorkspace, v as syncedFields } from "./workspace-DJ2dh3q7.mjs";
1
+ export { t as BunInstallError, d as applyManifestToLockfile, n as checkNestedWorkspace, g as diffManifests, o as findDirectDependencyMismatches, s as findOwnDependencyChanges, c as findTransitiveMismatches, l as findUncoveredWorkspaces, u as findWorkspaceMismatches, r as isInSync, i as loadNestedWorkspace, f as mergeLockfiles, p as parseLockfile, m as relocateLockfile, h as supportedLockfileVersions, _ as syncManifest, a as syncNestedWorkspace, v as syncedFields } from "./workspace-BlJ1qGiO.mjs";
@@ -245,7 +245,8 @@ function setCatalogs(manifest, catalogs) {
245
245
  *
246
246
  * A nested entry replaces a host entry with the same name, while the other host entries stay.
247
247
  * Entries synced earlier and dropped by the nested repository are removed.
248
- * Throws when the host and the nested repository patch the same package version.
248
+ * Throws when the host and the nested repository patch the same package version,
249
+ * and when either side defines `resolutions` while `overrides` are synced.
249
250
  */
250
251
  function syncManifest(host, nested) {
251
252
  const config = readConfig(host);
@@ -280,6 +281,7 @@ function syncManifest(host, nested) {
280
281
  }
281
282
  manifest = setCatalogs(manifest, catalogs);
282
283
  if (config.fields.includes("overrides")) {
284
+ for (const [source, resolutions] of [["The host package.json", host.resolutions], [`${config.dir}/package.json`, nested.resolutions]]) if (resolutions && Object.keys(resolutions).length) throw new Error(`${source} defines "resolutions", which Bun ignores once "overrides" is defined; move its entries to "overrides"`);
283
285
  const merged = mergeEntries("overrides", host.overrides, nested.overrides, synced.overrides);
284
286
  manifest.overrides = merged.entries;
285
287
  synced.overrides = merged.synced;
@@ -304,7 +306,8 @@ function syncManifest(host, nested) {
304
306
  } : rest;
305
307
  return {
306
308
  manifest,
307
- overridden
309
+ overridden,
310
+ addedTrustedDependencies: host.trustedDependencies === void 0 && manifest.trustedDependencies !== void 0
308
311
  };
309
312
  }
310
313
  /**
@@ -800,15 +803,16 @@ async function runBunInstall(root, args = []) {
800
803
  async function syncNestedWorkspace(root, options = {}) {
801
804
  const workspace = await loadNestedWorkspace(root);
802
805
  assertWorkspacesCovered(workspace);
803
- const { manifest, overridden } = syncManifest(workspace.manifest, workspace.nestedManifest);
806
+ const manifestSync = syncManifest(workspace.manifest, workspace.nestedManifest);
807
+ const { manifest } = manifestSync;
804
808
  const manifestText = formatJson(manifest, workspace.manifestText);
805
809
  if (manifestText !== workspace.manifestText) await Bun.write(path.join(root, "package.json"), manifestText);
810
+ options.onManifestSync?.(manifestSync);
806
811
  const merged = mergeLockfiles(applyManifestToLockfile(workspace.lock, manifest), workspace.nestedLock, workspace.config.dir);
807
812
  await Bun.write(path.join(root, "bun.lock"), renderBunLock(merged));
808
813
  await runBunInstall(root, options.installArgs);
809
814
  const synced = await loadNestedWorkspace(root);
810
815
  return {
811
- overridden,
812
816
  ownDependencyChanges: findOwnDependencyChanges(workspace.lock, synced.lock, workspace.nestedLock, workspace.config.dir),
813
817
  check: checkNestedWorkspace(synced)
814
818
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-nested-workspace",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Embed a checkout of another Bun workspace repository into a Bun workspace and keep the root package.json and bun.lock in sync with it",
5
5
  "keywords": [
6
6
  "bun",