bun-nested-workspace 0.0.0-stage → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilya Semenov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,162 @@
1
- # Temporary Holding Version
1
+ # bun-nested-workspace
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Embed a checkout of another Bun workspace repository into a subdirectory of your Bun workspace, and keep the root `package.json` and `bun.lock` of the host in sync with it.
4
+
5
+ The nested repository stays a self-contained project with its own root `package.json` and `bun.lock`.
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.
8
+ Bun reads catalogs, overrides, patches, and trusted dependencies from the workspace root only, and resolves the whole workspace from a single lockfile.
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.
10
+
11
+ ## Install
12
+
13
+ The CLI runs before the host dependencies are installed, so run it with `bunx`:
14
+
15
+ ```sh
16
+ bunx bun-nested-workspace
17
+ ```
18
+
19
+ It requires Bun 1.2.19 or later, the first version that resolves `catalog:` dependencies from both catalog locations.
20
+
21
+ ## Setup
22
+
23
+ Add the nested packages to the host workspaces and name the nested directory in the `bun-nested-workspace` field of the host `package.json`:
24
+
25
+ ```json
26
+ {
27
+ "workspaces": ["apps/*", "nested/apps/*", "nested/packages/*"],
28
+ "bun-nested-workspace": {
29
+ "dir": "nested"
30
+ }
31
+ }
32
+ ```
33
+
34
+ Both modes fail when the host `workspaces` patterns miss a workspace package of the nested repository.
35
+
36
+ The config takes these fields:
37
+
38
+ - `dir`: the nested checkout directory, relative to the host root.
39
+ - `fields`: the root fields to sync, a subset of `catalog`, `catalogs`, `overrides`, `patchedDependencies`, and `trustedDependencies`; all of them when omitted.
40
+ - `synced`: the names of entries that came from the nested repository, maintained by the sync; commit it and do not edit it by hand.
41
+
42
+ ## Sync
43
+
44
+ Run the CLI in the host root after switching the nested checkout to another commit:
45
+
46
+ ```sh
47
+ bunx bun-nested-workspace
48
+ ```
49
+
50
+ The sync:
51
+
52
+ 1. Merges the configured root fields of the nested `package.json` into the host `package.json`.
53
+ 2. Merges the nested `bun.lock` into the host `bun.lock`.
54
+ 3. Runs `bun install` with the CLI arguments, such as `--lockfile-only` or `--ignore-scripts`.
55
+ 4. Fails if the install re-resolved own dependencies of host packages, or if the host does not resolve the nested packages as the nested lockfile does.
56
+
57
+ Commit `package.json` and `bun.lock` after the sync.
58
+
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:
62
+
63
+ ```dockerfile
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
73
+ ```
74
+
75
+ Exclude `node_modules` and other large directories from the build context with `.dockerignore`.
76
+
77
+ ### Root fields
78
+
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`.
80
+ An array `workspaces` field becomes an object when a catalog needs it.
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`.
83
+ - `trustedDependencies` stays in the host `package.json`, even empty, when either side defines it.
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.
86
+ - `patchedDependencies` paths are rewritten through the nested directory, such as `nested/patches/exceljs@4.4.0.patch`.
87
+
88
+ A nested entry replaces a host entry with the same name, while the other host entries stay.
89
+ The sync lists the host entries it replaced.
90
+ A host entry equal to the nested one stays the host's own, so it survives when the nested repository drops it.
91
+ An entry that came from the nested repository and is no longer there is removed; for patches, these are the patches inside the nested directory.
92
+ A package version patched by both the host and the nested repository is an error.
93
+
94
+ Bun warns when a nested patch targets a package version that the host does not install, such as a patched tool used only by the nested root.
95
+
96
+ ### Lockfile
97
+
98
+ The nested lockfile entries replace the host entries, so the nested packages keep their versions.
99
+ When a replaced entry no longer satisfies a dependency of a host package, the host package keeps its previous resolution as a nested lockfile entry.
100
+ When neither satisfies the range, which Bun lockfiles occasionally contain, the dependency gets the highest satisfying version recorded in either lockfile.
101
+ Ranges are checked as `bun install` applies them: an override replaces the declared range, and an `npm:` alias requires the package it names.
102
+ `bun install` then resolves new dependencies of host packages and drops unused entries.
103
+
104
+ After the install, the sync compares the direct dependencies of host packages with the original host lockfile.
105
+ A dependency may only change to the version that the nested lockfile resolves at its root, and only within the host range.
106
+ Any other change means either that the host lockfile did not pin the host dependencies, for example after an edit of a host `package.json` without `bun install`, or that the lockfile cannot record the host version next to the nested one.
107
+ The latter happens to a host workspace without a name, because Bun records workspace-specific versions under the workspace name; name the workspace or align its range with the nested version.
108
+
109
+ Only `bun.lock` with `lockfileVersion` 1 or 2 is supported, because the lockfile format is internal to Bun.
110
+
111
+ ## Check
112
+
113
+ ```sh
114
+ bunx bun-nested-workspace --check
115
+ ```
116
+
117
+ The check writes nothing, does not run `bun install`, and exits with code 1 when the host files lag behind the nested checkout.
118
+ It reads the root `package.json` and `bun.lock` of the host and of the nested checkout only, so it relies on the nested `bun.lock` being up to date with the nested packages; `bun install --frozen-lockfile` in the nested directory verifies that.
119
+ Use it in a non-blocking CI job that signals when a sync commit is due.
120
+
121
+ It reports:
122
+
123
+ - synced root fields of the host `package.json` that differ from the result of a sync;
124
+ - nested workspaces whose `bun.lock` entries differ from the nested lockfile, which covers changed dependency declarations and added or removed packages;
125
+ - direct dependencies of nested packages that the host resolves to other versions than the nested lockfile;
126
+ - transitive dependencies of nested packages that the host resolves to other versions than the nested lockfile.
127
+
128
+ The transitive check walks the dependency edges from the nested packages through both lockfiles at once and compares the resolved version and integrity of each edge.
129
+ A different hoisting layout of the same versions is therefore not a difference.
130
+
131
+ Limitations:
132
+
133
+ - Peer dependencies are not walked.
134
+ - Edges that the nested lockfile itself resolves outside their range are skipped, because the sync repairs them in the host.
135
+ - The check does not verify that the host lockfile matches the host packages; `bun install --frozen-lockfile` does.
136
+ - The check does not predict whether a sync would re-resolve own dependencies of host packages; only the sync detects it after `bun install`.
137
+
138
+ ## API
139
+
140
+ The package exports the functions the CLI is built from:
141
+
142
+ ```ts
143
+ import {
144
+ checkNestedWorkspace,
145
+ isInSync,
146
+ loadNestedWorkspace,
147
+ syncNestedWorkspace,
148
+ } from "bun-nested-workspace"
149
+
150
+ const check = checkNestedWorkspace(await loadNestedWorkspace(process.cwd()))
151
+ if (!isInSync(check)) {
152
+ const result = await syncNestedWorkspace(process.cwd(), { installArgs: ["--lockfile-only"] })
153
+ if (result.ownDependencyChanges.length || !isInSync(result.check)) {
154
+ throw new Error("The sync changed host dependencies or did not keep the nested resolutions")
155
+ }
156
+ }
157
+ ```
158
+
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.
161
+
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.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.mjs ADDED
@@ -0,0 +1,100 @@
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-BlJ1qGiO.mjs";
3
+ // src/cli.ts
4
+ const usage = `Usage:
5
+ bun-nested-workspace [bun install options] Sync package.json and bun.lock, then run bun install
6
+ bun-nested-workspace --check Exit with 1 if package.json or bun.lock lag behind the nested checkout
7
+
8
+ Configure the nested directory in the "bun-nested-workspace" field of the host package.json.`;
9
+ /** Runs the CLI with the given arguments in the given host root and returns the exit code. */
10
+ async function runCli(args, root = process.cwd()) {
11
+ if (args.includes("--help") || args.includes("-h")) {
12
+ console.log(usage);
13
+ return 0;
14
+ }
15
+ try {
16
+ if (args.includes("--check")) {
17
+ const extra = args.filter((arg) => arg !== "--check");
18
+ if (extra.length) {
19
+ console.error(`--check takes no other options, got ${extra.join(" ")}\n\n${usage}`);
20
+ return 1;
21
+ }
22
+ return await runCheck(root);
23
+ }
24
+ return await runSync(root, args);
25
+ } catch (error) {
26
+ if (error instanceof BunInstallError) return error.exitCode;
27
+ console.error(getErrorMessage(error));
28
+ return 1;
29
+ }
30
+ }
31
+ async function runCheck(root) {
32
+ const workspace = await loadNestedWorkspace(root);
33
+ const result = checkNestedWorkspace(workspace);
34
+ if (isInSync(result)) {
35
+ console.log(`package.json and bun.lock are in sync with ${workspace.config.dir}`);
36
+ return 0;
37
+ }
38
+ console.error([
39
+ `package.json and bun.lock lag behind ${workspace.config.dir}:`,
40
+ ...formatCheckResult(result),
41
+ "Run bun-nested-workspace without --check and commit package.json and bun.lock."
42
+ ].join("\n"));
43
+ return 1;
44
+ }
45
+ async function runSync(root, installArgs) {
46
+ const { ownDependencyChanges, check } = await syncNestedWorkspace(root, {
47
+ installArgs,
48
+ onManifestSync: printManifestSync
49
+ });
50
+ let exitCode = 0;
51
+ if (ownDependencyChanges.length) {
52
+ console.error([
53
+ "The sync changed own dependencies of host packages.",
54
+ "Either the host bun.lock did not pin them: restore bun.lock, run bun install, commit bun.lock, and sync again.",
55
+ "Or the nested root resolves them outside the host range: align the host range with the nested version.",
56
+ ...ownDependencyChanges.map(({ workspace, dependency, expected, actual }) => formatChange(`${workspace} > ${dependency}`, expected, actual))
57
+ ].join("\n"));
58
+ exitCode = 1;
59
+ }
60
+ if (!isInSync(check)) {
61
+ console.error(["bun install did not keep the nested resolutions:", ...formatCheckResult(check)].join("\n"));
62
+ exitCode = 1;
63
+ }
64
+ return exitCode;
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
+ }
74
+ /** Formats the differences of a failed check as indented lines grouped by kind. */
75
+ function formatCheckResult(result) {
76
+ const section = (title, lines) => lines.length ? [` ${title}:`, ...lines.map((line) => ` ${line}`)] : [];
77
+ return [
78
+ ...section("package.json", result.manifest.map(formatDifference)),
79
+ ...section("bun.lock workspaces", result.workspaces.map((workspacePath) => ` ${workspacePath}`)),
80
+ ...section("direct dependencies", result.directDependencies.map(({ workspace, dependency, expected, actual }) => formatChange(`${workspace} > ${dependency}`, actual, expected))),
81
+ ...section("transitive dependencies", result.transitiveDependencies.map(({ path, expected, actual }) => formatChange(path.join(" > "), actual, expected)))
82
+ ];
83
+ }
84
+ function formatDifference({ path, expected, actual }) {
85
+ const format = (value) => value === void 0 ? void 0 : JSON.stringify(value);
86
+ return formatChange(path, format(actual), format(expected));
87
+ }
88
+ function formatOverridden({ field, name, hostValue, nestedValue }) {
89
+ return formatChange(`${field}.${name}`, JSON.stringify(hostValue), JSON.stringify(nestedValue));
90
+ }
91
+ /**
92
+ * Formats a value change as an indented line, with a dash for a missing value.
93
+ *
94
+ * Lockfile entries of the same version can still differ in their registry or integrity.
95
+ */
96
+ function formatChange(label, from, to) {
97
+ return from === to ? ` ${label}: ${from} (registry or integrity differs)` : ` ${label}: ${from ?? "—"} → ${to ?? "—"}`;
98
+ }
99
+ if (import.meta.main) process.exit(await runCli(process.argv.slice(2)));
100
+ export {};
@@ -0,0 +1,304 @@
1
+ type DependencyMap = Record<string, string>;
2
+ type BunLockWorkspace = {
3
+ /**
4
+ * Optional in Bun lockfiles. When present, it is also used as the lookup
5
+ * context for workspace-scoped lock keys.
6
+ */
7
+ name?: string;
8
+ dependencies?: DependencyMap;
9
+ devDependencies?: DependencyMap;
10
+ optionalDependencies?: DependencyMap;
11
+ peerDependencies?: DependencyMap;
12
+ };
13
+ /**
14
+ * Metadata stored in a Bun package tuple.
15
+ *
16
+ * `bundled` is context-specific in Bun lockfiles: it belongs to entries under
17
+ * the package that bundles them and must not be blindly copied to another
18
+ * lock key when reusing a package tuple as a dedupe template.
19
+ */
20
+ type BunPackageMeta = {
21
+ dependencies?: DependencyMap;
22
+ optionalDependencies?: DependencyMap;
23
+ peerDependencies?: DependencyMap;
24
+ /** Peer dependency names Bun treats as optional. */
25
+ optionalPeers?: string[];
26
+ /** True only inside the package subtree that actually bundles this entry. */
27
+ bundled?: boolean;
28
+ bin?: string | Record<string, string>;
29
+ os?: string | string[];
30
+ cpu?: string | string[];
31
+ };
32
+ /**
33
+ * Bun lockfile package tuple.
34
+ *
35
+ * Registry packages use `[spec, resolved, metadata, integrity]`, while git
36
+ * packages use `[spec, metadata, resolved, integrity]`. Bun omits trailing
37
+ * values freely in either form.
38
+ */
39
+ type BunRegistryPackageEntry = [spec: string, resolved?: string, meta?: BunPackageMeta, integrity?: string];
40
+ type BunGitPackageEntry = [spec: string, meta: BunPackageMeta, resolved?: string, integrity?: string];
41
+ type BunPackageEntry = BunRegistryPackageEntry | BunGitPackageEntry;
42
+ /** Parsed `bun.lock` sections that the tools read; Bun may preserve additional top-level keys. */
43
+ type BunLockFile = {
44
+ lockfileVersion?: number;
45
+ configVersion?: number;
46
+ workspaces?: Record<string, BunLockWorkspace>;
47
+ /** Default catalog of the root `package.json`. */
48
+ catalog?: DependencyMap;
49
+ /** Named catalogs of the root `package.json`. */
50
+ catalogs?: Record<string, DependencyMap>;
51
+ /** Canonical map Bun writes for package.json overrides and resolutions. */
52
+ overrides?: DependencyMap;
53
+ /** Package specs whose local patches may change their dependency declarations. */
54
+ patchedDependencies?: Record<string, string>;
55
+ packages?: Record<string, BunPackageEntry>;
56
+ };
57
+ /** Root `package.json` fields that can be synced from the nested repository. */
58
+ type SyncedField = "catalog" | "catalogs" | "overrides" | "patchedDependencies" | "trustedDependencies";
59
+ /** All root fields in sync order; the default for `NestedWorkspaceConfig.fields`. */
60
+ export declare const syncedFields: readonly SyncedField[];
61
+ /** Field of the host `package.json` that holds `NestedWorkspaceConfig`. */
62
+ declare const configField = "bun-nested-workspace";
63
+ /**
64
+ * Names of host entries that came from the nested repository during the last sync.
65
+ *
66
+ * The sync maintains these lists to tell synced entries from the host's own ones,
67
+ * so it can remove entries that the nested repository dropped.
68
+ * Patches need no list: a synced patch is the one whose path lies inside the nested directory.
69
+ */
70
+ interface SyncedEntries {
71
+ catalog?: string[];
72
+ catalogs?: Record<string, string[]>;
73
+ overrides?: string[];
74
+ trustedDependencies?: string[];
75
+ }
76
+ /** Configuration stored in the `bun-nested-workspace` field of the host `package.json`. */
77
+ interface NestedWorkspaceConfig {
78
+ /** Directory of the nested repository checkout, relative to the host root. */
79
+ dir: string;
80
+ /** Root fields to sync; all of `syncedFields` when omitted. */
81
+ fields?: SyncedField[];
82
+ /** Maintained by the sync; do not edit by hand. */
83
+ synced?: SyncedEntries;
84
+ }
85
+ /** Validated `NestedWorkspaceConfig` with defaults applied. */
86
+ interface ResolvedConfig {
87
+ /** Normalized relative POSIX path without a trailing slash. */
88
+ dir: string;
89
+ fields: readonly SyncedField[];
90
+ synced: SyncedEntries;
91
+ }
92
+ type Catalog = Record<string, string>;
93
+ /** Root `package.json` fields this package reads or rewrites; other fields are preserved as is. */
94
+ interface PackageManifest {
95
+ workspaces?: string[] | WorkspacesConfig;
96
+ catalog?: Catalog;
97
+ catalogs?: Record<string, Catalog>;
98
+ overrides?: Record<string, string>;
99
+ resolutions?: Record<string, string>;
100
+ patchedDependencies?: Record<string, string>;
101
+ trustedDependencies?: string[];
102
+ [configField]?: NestedWorkspaceConfig;
103
+ [key: string]: unknown;
104
+ }
105
+ /** Object form of the `workspaces` field of a root `package.json`. */
106
+ interface WorkspacesConfig {
107
+ packages?: string[];
108
+ catalog?: Catalog;
109
+ catalogs?: Record<string, Catalog>;
110
+ [key: string]: unknown;
111
+ }
112
+ /** Host entry that a nested entry with the same name replaced during the sync. */
113
+ interface OverriddenEntry {
114
+ /** Field path, such as `catalog`, `catalogs.testing`, or `overrides`. */
115
+ field: string;
116
+ name: string;
117
+ hostValue: string;
118
+ nestedValue: string;
119
+ }
120
+ /** Result of `syncManifest`. */
121
+ interface ManifestSync {
122
+ manifest: PackageManifest;
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;
131
+ }
132
+ /**
133
+ * Returns the host root `package.json` with the configured root fields of the nested `package.json` merged in.
134
+ *
135
+ * A nested entry replaces a host entry with the same name, while the other host entries stay.
136
+ * Entries synced earlier and dropped by the nested repository are removed.
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.
139
+ */
140
+ export declare function syncManifest(host: PackageManifest, nested: PackageManifest): ManifestSync;
141
+ /** Difference between the expected and the actual value at a `package.json` path. */
142
+ interface ManifestDifference {
143
+ /** Dot-separated path, such as `catalog.h3`. */
144
+ path: string;
145
+ expected?: unknown;
146
+ actual?: unknown;
147
+ }
148
+ /**
149
+ * Returns the differences of the synced root fields and the synced entry lists between two manifests.
150
+ *
151
+ * Key order and the order of `trustedDependencies` are ignored.
152
+ */
153
+ export declare function diffManifests(expected: PackageManifest, actual: PackageManifest): ManifestDifference[];
154
+ /** Parsed `bun.lock` with a supported `lockfileVersion`. */
155
+ type Lockfile = BunLockFile & {
156
+ lockfileVersion: number;
157
+ workspaces: Record<string, BunLockWorkspace>;
158
+ packages: Record<string, BunPackageEntry>;
159
+ };
160
+ /**
161
+ * `bun.lock` format versions this package understands.
162
+ *
163
+ * The format is internal to Bun, so other versions are rejected instead of being rewritten blindly.
164
+ */
165
+ export declare const supportedLockfileVersions: readonly number[];
166
+ /** Parses `bun.lock` text and rejects lockfile versions outside `supportedLockfileVersions`. */
167
+ export declare function parseLockfile(text: string, source?: string): Lockfile;
168
+ /**
169
+ * Returns the nested lockfile with workspace and local paths relative to the host root.
170
+ *
171
+ * The nested root workspace is dropped, because it is not a workspace of the host.
172
+ */
173
+ export declare function relocateLockfile(lock: Lockfile, dir: string): Lockfile;
174
+ /**
175
+ * Returns the lockfile with root fields that affect resolution taken from the root `package.json`.
176
+ *
177
+ * `bun install` re-resolves dependencies whose catalog entry, override, or patch differs from the lockfile,
178
+ * so the merged lockfile must already record the synced root fields to keep the nested resolutions.
179
+ */
180
+ export declare function applyManifestToLockfile(lock: Lockfile, manifest: PackageManifest): Lockfile;
181
+ /**
182
+ * Merges the host lockfile with the relocated nested lockfile.
183
+ *
184
+ * Nested entries override host entries, so the nested packages keep the versions of the nested lockfile.
185
+ * A root entry overridden by the nested lockfile might not satisfy a dependency of a host package:
186
+ * such a dependency gets back its previous host resolution as a nested entry, so `bun install` keeps the host version
187
+ * instead of resolving the range again.
188
+ * A host workspace without a name cannot have nested entries, so `bun install` resolves its dependency again.
189
+ * When the previous host resolution does not satisfy the range either, which Bun lockfiles occasionally contain,
190
+ * the dependency gets the highest satisfying version recorded in either lockfile.
191
+ * Catalog dependencies are checked against the catalogs of the host lockfile, so apply the synced root fields
192
+ * to it first.
193
+ */
194
+ export declare function mergeLockfiles(lock: Lockfile, nestedLock: Lockfile, dir: string): Lockfile;
195
+ /** Dependency of a workspace package resolved differently than expected. */
196
+ interface DependencyMismatch {
197
+ /** Workspace package name, or its path when the package has no name. */
198
+ workspace: string;
199
+ dependency: string;
200
+ /** Expected resolution, such as `name@1.2.3`; `undefined` when the dependency must not resolve. */
201
+ expected?: string;
202
+ /** Actual resolution; `undefined` when the dependency does not resolve. */
203
+ actual?: string;
204
+ }
205
+ /** Transitive dependency resolved differently than expected. */
206
+ interface PackageMismatch {
207
+ /** Dependency chain from the nested workspace package, such as `["@nested/web", "nuxt", "@nuxt/kit"]`. */
208
+ path: string[];
209
+ /** Expected resolution, such as `name@1.2.3`. */
210
+ expected: string;
211
+ /** Actual resolution; `undefined` when the dependency does not resolve. */
212
+ actual?: string;
213
+ }
214
+ /** Returns the nested workspace paths that the host `workspaces` patterns do not include. */
215
+ export declare function findUncoveredWorkspaces(manifest: PackageManifest, nestedLock: Lockfile): string[];
216
+ /**
217
+ * Returns the paths of nested workspaces whose host lockfile entries differ from the relocated nested lockfile,
218
+ * including host entries of workspaces that the nested repository no longer has.
219
+ */
220
+ export declare function findWorkspaceMismatches(lock: Lockfile, nestedLock: Lockfile, dir: string): string[];
221
+ /**
222
+ * Returns the direct dependencies of nested workspaces that the host lockfile resolves differently than the nested one.
223
+ *
224
+ * Entries are compared like `findTransitiveMismatches` compares them, so a different integrity is a mismatch too.
225
+ */
226
+ export declare function findDirectDependencyMismatches(lock: Lockfile, nestedLock: Lockfile): DependencyMismatch[];
227
+ /**
228
+ * Returns the transitive dependencies of nested workspaces that the host lockfile resolves differently than the nested one.
229
+ *
230
+ * Walks the dependency edges from the nested workspaces through both lockfiles at once,
231
+ * so a different hoisting layout of the same versions is not a mismatch.
232
+ * Direct dependencies are covered by `findDirectDependencyMismatches`, and peer dependencies are not walked.
233
+ * Edges that the nested lockfile resolves outside their range are skipped, because the merge repairs them.
234
+ * Each mismatch is reported once, with the first dependency chain that reaches it, and its subtree is skipped.
235
+ */
236
+ export declare function findTransitiveMismatches(lock: Lockfile, nestedLock: Lockfile): PackageMismatch[];
237
+ /**
238
+ * Returns the external direct dependencies of host workspaces whose resolution differs between two host lockfiles.
239
+ *
240
+ * A dependency that changed to the root resolution of the nested lockfile is not reported, because the nested versions win,
241
+ * unless that resolution does not satisfy the host range.
242
+ */
243
+ export declare function findOwnDependencyChanges(before: Lockfile, after: Lockfile, nestedLock: Lockfile, dir: string): DependencyMismatch[];
244
+ /** Host and nested root files loaded from disk. */
245
+ interface NestedWorkspace {
246
+ /** Absolute path of the host root. */
247
+ root: string;
248
+ config: ResolvedConfig;
249
+ manifest: PackageManifest;
250
+ /** Original text of the host `package.json`, used to preserve its formatting. */
251
+ manifestText: string;
252
+ nestedManifest: PackageManifest;
253
+ lock: Lockfile;
254
+ /** Nested lockfile relocated with `relocateLockfile`. */
255
+ nestedLock: Lockfile;
256
+ }
257
+ /** Loads the root `package.json` and `bun.lock` files of the host and of the nested checkout. */
258
+ export declare function loadNestedWorkspace(root: string): Promise<NestedWorkspace>;
259
+ /** Differences between the host files and the nested checkout; all lists are empty when the host is in sync. */
260
+ interface CheckResult {
261
+ /** Synced root fields of the host `package.json` that differ from the nested ones. */
262
+ manifest: ManifestDifference[];
263
+ /** Nested workspaces whose host `bun.lock` entries differ from the nested `bun.lock`. */
264
+ workspaces: string[];
265
+ /** Direct dependencies of nested workspaces resolved differently than in the nested `bun.lock`. */
266
+ directDependencies: DependencyMismatch[];
267
+ /** Transitive dependencies of nested workspaces resolved differently than in the nested `bun.lock`. */
268
+ transitiveDependencies: PackageMismatch[];
269
+ }
270
+ /** Returns whether a check found no differences. */
271
+ export declare function isInSync(result: CheckResult): boolean;
272
+ /** Compares the host files with the nested checkout without writing anything or running `bun install`. */
273
+ export declare function checkNestedWorkspace(workspace: NestedWorkspace): CheckResult;
274
+ /** Error thrown when `bun install` exits with a non-zero code. */
275
+ export declare class BunInstallError extends Error {
276
+ readonly exitCode: number;
277
+ constructor(exitCode: number);
278
+ }
279
+ /** Options of `syncNestedWorkspace`. */
280
+ interface SyncOptions {
281
+ /** Arguments passed to `bun install`, such as `--lockfile-only`. */
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;
289
+ }
290
+ /** Result of `syncNestedWorkspace`. */
291
+ interface SyncResult {
292
+ /** Own dependencies of host workspaces that the sync re-resolved; the sync failed when this is not empty. */
293
+ ownDependencyChanges: DependencyMismatch[];
294
+ /** Check of the synced files; the sync failed when it is not in sync. */
295
+ check: CheckResult;
296
+ }
297
+ /**
298
+ * Syncs the host root `package.json` and `bun.lock` with the nested checkout.
299
+ *
300
+ * Writes the synced `package.json`, writes the merged `bun.lock`, runs `bun install`,
301
+ * and then compares the result with the nested checkout and with the original host resolutions.
302
+ */
303
+ export declare function syncNestedWorkspace(root: string, options?: SyncOptions): Promise<SyncResult>;
304
+ export type { CheckResult, DependencyMismatch, Lockfile, ManifestDifference, ManifestSync, NestedWorkspace, NestedWorkspaceConfig, OverriddenEntry, PackageManifest, PackageMismatch, ResolvedConfig, SyncOptions, SyncResult, SyncedEntries, SyncedField, WorkspacesConfig };
package/dist/index.mjs ADDED
@@ -0,0 +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-BlJ1qGiO.mjs";