bun-nested-workspace 0.3.2 → 0.5.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 +13 -6
- package/dist/cli.mjs +20 -3
- package/dist/index.d.mts +31 -12
- package/dist/index.mjs +1 -1
- package/dist/{workspace-ChlHn5I8.mjs → workspace-CC5KeN8i.mjs} +919 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,8 @@ Then update the nested lockfile with `bun install --lockfile-only` in the direct
|
|
|
51
51
|
bunx bun-nested-workspace
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
A nested repository without workspace packages updates its lockfile in a separate clone.
|
|
55
|
+
|
|
54
56
|
Do not run other `bun install`, `bun add`, or `bun remove` commands in the nested directory or in its package directories:
|
|
55
57
|
|
|
56
58
|
- In the nested root, Bun runs them for the host: it writes the host lockfile and leaves the nested one stale.
|
|
@@ -72,6 +74,7 @@ The sync:
|
|
|
72
74
|
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.
|
|
73
75
|
|
|
74
76
|
Commit `package.json` and `bun.lock` after the sync.
|
|
77
|
+
A failed sync restores `package.json` and `bun.lock` to their contents before the run, while `node_modules` stays as the install left it.
|
|
75
78
|
|
|
76
79
|
In a Docker build, run the sync with `--lockfile-only` in a stage that contains only the dependency files, before installing dependencies.
|
|
77
80
|
The dependency install runs the `prepare` script of the nested root unless it gets `--ignore-scripts`.
|
|
@@ -96,7 +99,9 @@ Exclude `node_modules` and other large directories from the build context with `
|
|
|
96
99
|
|
|
97
100
|
- `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`.
|
|
98
101
|
An array `workspaces` field becomes an object when a catalog needs it.
|
|
99
|
-
- `overrides`
|
|
102
|
+
- `overrides` are merged by their keys, so a group such as `"micromatch": { "picomatch": "^2.3.2" }` replaces the host group with the same key as a whole.
|
|
103
|
+
The sync requires each `bun.lock` to be up to date with the overrides of its `package.json`.
|
|
104
|
+
- `trustedDependencies` are merged by package name.
|
|
100
105
|
- `resolutions` on either side is an error while `overrides` are synced: Bun ignores `resolutions` once `overrides` is defined, so move its entries to `overrides`.
|
|
101
106
|
- `trustedDependencies` stays in the host `package.json`, even empty, when either side defines it.
|
|
102
107
|
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.
|
|
@@ -104,7 +109,7 @@ Exclude `node_modules` and other large directories from the build context with `
|
|
|
104
109
|
- `patchedDependencies` paths are rewritten through the nested directory, such as `nested/patches/exceljs@4.4.0.patch`.
|
|
105
110
|
Bun warns on every install that it ignores `patchedDependencies` of the nested root package; keep them there, because the host applies the synced copies.
|
|
106
111
|
|
|
107
|
-
A nested entry replaces a host entry with the same
|
|
112
|
+
A nested entry replaces a host entry with the same key, while the other host entries stay.
|
|
108
113
|
The sync lists the host entries it replaced.
|
|
109
114
|
A host entry equal to the nested one stays the host's own, so it survives when the nested repository drops it.
|
|
110
115
|
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.
|
|
@@ -123,7 +128,7 @@ A dependency may only change to the version that the nested lockfile hoists to i
|
|
|
123
128
|
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.
|
|
124
129
|
The latter happens to a host workspace other than the root without a name, because Bun records workspace-specific versions under the workspace name; name the workspace or align its range with the nested version.
|
|
125
130
|
|
|
126
|
-
Only `bun.lock` with `lockfileVersion` 1 or
|
|
131
|
+
Only `bun.lock` with `lockfileVersion` 1, 2, or 3 is supported, because the lockfile format is internal to Bun.
|
|
127
132
|
|
|
128
133
|
## Check
|
|
129
134
|
|
|
@@ -132,7 +137,8 @@ bunx bun-nested-workspace --check
|
|
|
132
137
|
```
|
|
133
138
|
|
|
134
139
|
The check writes nothing, does not run `bun install`, and exits with code 1 when the host files lag behind the nested checkout.
|
|
135
|
-
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
|
|
140
|
+
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.
|
|
141
|
+
Verify that in the CI of the nested repository with `bun install --lockfile-only` followed by `git diff --exit-code bun.lock`: `bun install --frozen-lockfile` misses some changes, such as a narrowed range that the locked version still satisfies.
|
|
136
142
|
Use it in a non-blocking CI job that signals when a sync commit is due.
|
|
137
143
|
|
|
138
144
|
It reports:
|
|
@@ -149,7 +155,7 @@ Limitations:
|
|
|
149
155
|
|
|
150
156
|
- Peer dependencies are not walked.
|
|
151
157
|
- Edges that the nested lockfile itself resolves outside their range are skipped, because the sync repairs them in the host.
|
|
152
|
-
- The check does not verify that the host lockfile matches the host packages; `bun install --
|
|
158
|
+
- The check does not verify that the host lockfile matches the host packages; `bun install --lockfile-only` followed by `git diff --exit-code bun.lock` does.
|
|
153
159
|
- The check does not predict whether a sync would re-resolve own dependencies of host packages; only the sync detects it after `bun install`.
|
|
154
160
|
|
|
155
161
|
## API
|
|
@@ -173,7 +179,8 @@ if (!isInSync(check)) {
|
|
|
173
179
|
}
|
|
174
180
|
```
|
|
175
181
|
|
|
176
|
-
`syncNestedWorkspace` writes the files and runs `bun install` before it checks the result
|
|
182
|
+
`syncNestedWorkspace` writes the files and runs `bun install` before it checks the result.
|
|
183
|
+
When `bun install` fails or the result shows differences, it restores both files before it throws or returns, and throws an error saying so when it cannot restore them.
|
|
177
184
|
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.
|
|
178
185
|
|
|
179
186
|
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,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as isNestedWorkspace, i as isInSync, n as checkNestedWorkspace, o as loadNestedWorkspace, r as findNestedPackageDir, s as syncNestedWorkspace, t as BunInstallError, x as getErrorMessage } from "./workspace-CC5KeN8i.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
|
|
6
6
|
bun-nested-workspace --check Exit with 1 if package.json or bun.lock lag behind the nested checkout
|
|
7
7
|
|
|
8
8
|
Configure the nested directory in the "bun-nested-workspace" field of the host package.json.`;
|
|
9
|
+
const restoredMessage = "Restored package.json and bun.lock to their contents before the sync.";
|
|
9
10
|
/** Runs the CLI with the given arguments in the given host root and returns the exit code. */
|
|
10
11
|
async function runCli(args, root = process.cwd()) {
|
|
11
12
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -23,7 +24,10 @@ async function runCli(args, root = process.cwd()) {
|
|
|
23
24
|
}
|
|
24
25
|
return await runSync(root, args);
|
|
25
26
|
} catch (error) {
|
|
26
|
-
if (error instanceof BunInstallError)
|
|
27
|
+
if (error instanceof BunInstallError) {
|
|
28
|
+
console.error(restoredMessage);
|
|
29
|
+
return error.exitCode;
|
|
30
|
+
}
|
|
27
31
|
console.error(getErrorMessage(error));
|
|
28
32
|
return 1;
|
|
29
33
|
}
|
|
@@ -51,7 +55,7 @@ async function runSync(root, installArgs) {
|
|
|
51
55
|
if (ownDependencyChanges.length) {
|
|
52
56
|
console.error([
|
|
53
57
|
"The sync changed own dependencies of host packages.",
|
|
54
|
-
"Either the host bun.lock did not pin them:
|
|
58
|
+
"Either the host bun.lock did not pin them: run bun install, commit bun.lock, and sync again.",
|
|
55
59
|
"Or the nested lockfile resolves them outside the host range: align the host range with the nested version.",
|
|
56
60
|
...ownDependencyChanges.map(({ workspace, dependency, expected, actual }) => formatChange(`${workspace} > ${dependency}`, expected, actual))
|
|
57
61
|
].join("\n"));
|
|
@@ -59,10 +63,23 @@ async function runSync(root, installArgs) {
|
|
|
59
63
|
}
|
|
60
64
|
if (!isInSync(check)) {
|
|
61
65
|
console.error(["bun install did not keep the nested resolutions:", ...formatCheckResult(check)].join("\n"));
|
|
66
|
+
const workspace = await loadNestedWorkspace(root);
|
|
67
|
+
if (check.workspaces.some((workspacePath) => isNestedWorkspace(workspace, workspacePath))) console.error(await formatNestedLockfileUpdate(workspace));
|
|
62
68
|
exitCode = 1;
|
|
63
69
|
}
|
|
70
|
+
if (exitCode) console.error(restoredMessage);
|
|
64
71
|
return exitCode;
|
|
65
72
|
}
|
|
73
|
+
/** Formats how to update a nested lockfile that lags behind the nested package.json files. */
|
|
74
|
+
async function formatNestedLockfileUpdate(workspace) {
|
|
75
|
+
const { dir } = workspace.config;
|
|
76
|
+
const packageDir = await findNestedPackageDir(workspace);
|
|
77
|
+
return packageDir ? `${dir}/bun.lock lags behind the package.json files of ${dir}. Update it and sync again:\n (cd ${quoteShellArgument(packageDir)} && bun install --lockfile-only)` : `${dir}/bun.lock lags behind the package.json files of ${dir}. Update it with bun install --lockfile-only in a separate clone of the nested repository and sync again.`;
|
|
78
|
+
}
|
|
79
|
+
/** Quotes a value for a POSIX shell when it contains characters other than those of plain paths. */
|
|
80
|
+
function quoteShellArgument(value) {
|
|
81
|
+
return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
82
|
+
}
|
|
66
83
|
function printManifestSync({ overridden, addedTrustedDependencies }) {
|
|
67
84
|
if (overridden.length) console.log(["Nested entries replaced host entries:", ...overridden.map(formatOverridden)].join("\n"));
|
|
68
85
|
if (addedTrustedDependencies) console.warn([
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import semver from "semver";
|
|
1
2
|
type DependencyMap = Record<string, string>;
|
|
2
3
|
type BunLockWorkspace = {
|
|
3
4
|
/**
|
|
@@ -5,6 +6,8 @@ type BunLockWorkspace = {
|
|
|
5
6
|
* context for workspace-scoped lock keys.
|
|
6
7
|
*/
|
|
7
8
|
name?: string;
|
|
9
|
+
/** Version of a workspace package; Bun leaves it out for the root workspace. */
|
|
10
|
+
version?: string;
|
|
8
11
|
dependencies?: DependencyMap;
|
|
9
12
|
devDependencies?: DependencyMap;
|
|
10
13
|
optionalDependencies?: DependencyMap;
|
|
@@ -48,8 +51,12 @@ type BunLockFile = {
|
|
|
48
51
|
catalog?: DependencyMap;
|
|
49
52
|
/** Named catalogs of the root `package.json`. */
|
|
50
53
|
catalogs?: Record<string, DependencyMap>;
|
|
51
|
-
/**
|
|
52
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Canonical map Bun writes for package.json overrides and resolutions.
|
|
56
|
+
*
|
|
57
|
+
* A string value overrides a package by name; an object value groups the rules scoped to a package or its range.
|
|
58
|
+
*/
|
|
59
|
+
overrides?: Record<string, string | DependencyMap>;
|
|
53
60
|
/** Package specs whose local patches may change their dependency declarations. */
|
|
54
61
|
patchedDependencies?: Record<string, string>;
|
|
55
62
|
packages?: Record<string, BunPackageEntry>;
|
|
@@ -90,6 +97,8 @@ interface ResolvedConfig {
|
|
|
90
97
|
synced: SyncedEntries;
|
|
91
98
|
}
|
|
92
99
|
type Catalog = Record<string, string>;
|
|
100
|
+
/** Value of an `overrides` entry: a version spec, or a group of rules keyed by `"."` and dependency selectors. */
|
|
101
|
+
type OverrideValue = string | Record<string, string>;
|
|
93
102
|
/** Root `package.json` fields this package reads or rewrites; other fields are preserved as is. */
|
|
94
103
|
interface PackageManifest {
|
|
95
104
|
dependencies?: Record<string, string>;
|
|
@@ -99,7 +108,7 @@ interface PackageManifest {
|
|
|
99
108
|
workspaces?: string[] | WorkspacesConfig;
|
|
100
109
|
catalog?: Catalog;
|
|
101
110
|
catalogs?: Record<string, Catalog>;
|
|
102
|
-
overrides?: Record<string,
|
|
111
|
+
overrides?: Record<string, OverrideValue>;
|
|
103
112
|
resolutions?: Record<string, string>;
|
|
104
113
|
patchedDependencies?: Record<string, string>;
|
|
105
114
|
trustedDependencies?: string[];
|
|
@@ -118,8 +127,8 @@ interface OverriddenEntry {
|
|
|
118
127
|
/** Field path, such as `catalog`, `catalogs.testing`, or `overrides`. */
|
|
119
128
|
field: string;
|
|
120
129
|
name: string;
|
|
121
|
-
hostValue:
|
|
122
|
-
nestedValue:
|
|
130
|
+
hostValue: OverrideValue;
|
|
131
|
+
nestedValue: OverrideValue;
|
|
123
132
|
}
|
|
124
133
|
/** Result of `syncManifest`. */
|
|
125
134
|
interface ManifestSync {
|
|
@@ -136,11 +145,13 @@ interface ManifestSync {
|
|
|
136
145
|
/**
|
|
137
146
|
* Returns the host root `package.json` with the configured root fields of the nested `package.json` merged in.
|
|
138
147
|
*
|
|
139
|
-
* A nested entry replaces a host entry with the same
|
|
148
|
+
* A nested entry replaces a host entry with the same key, while the other host entries stay.
|
|
140
149
|
* Entries synced earlier and dropped by the nested repository are removed.
|
|
141
150
|
* Throws when the host and the nested repository patch the same package version,
|
|
142
151
|
* when either side defines `resolutions` while `overrides` are synced,
|
|
143
|
-
*
|
|
152
|
+
* when an override is one that `readManifestOverrides` rejects, when two overrides select the same packages,
|
|
153
|
+
* when a synced `$name` override would refer to another range in the host root than in the nested root,
|
|
154
|
+
* and when a synced override selects the nested root by a range that its version satisfies.
|
|
144
155
|
*/
|
|
145
156
|
export declare function syncManifest(host: PackageManifest, nested: PackageManifest): ManifestSync;
|
|
146
157
|
/** Difference between the expected and the actual value at a `package.json` path. */
|
|
@@ -184,7 +195,7 @@ export declare function relocateLockfile(lock: Lockfile, dir: string, rootName?:
|
|
|
184
195
|
*
|
|
185
196
|
* `bun install` re-resolves dependencies whose catalog entry, override, or patch differs from the lockfile,
|
|
186
197
|
* so the merged lockfile must already record the synced root fields to keep the nested resolutions.
|
|
187
|
-
* Records
|
|
198
|
+
* Records overrides in the form that Bun writes to `bun.lock`, with `$name` values replaced by the range they refer to.
|
|
188
199
|
*/
|
|
189
200
|
export declare function applyManifestToLockfile(lock: Lockfile, manifest: PackageManifest): Lockfile;
|
|
190
201
|
/**
|
|
@@ -199,8 +210,9 @@ export declare function applyManifestToLockfile(lock: Lockfile, manifest: Packag
|
|
|
199
210
|
* so `bun install` resolves its dependency again.
|
|
200
211
|
* When the previous host resolution does not satisfy the range either, which Bun lockfiles occasionally contain,
|
|
201
212
|
* the dependency gets the highest satisfying version recorded in either lockfile.
|
|
202
|
-
*
|
|
213
|
+
* Dependencies are checked against the catalogs and overrides of the host lockfile, so apply the synced root fields
|
|
203
214
|
* to it first.
|
|
215
|
+
* The result has the higher `lockfileVersion` of the two, or version 1 when it has an entry that needs that version.
|
|
204
216
|
*/
|
|
205
217
|
export declare function mergeLockfiles(lock: Lockfile, nestedLock: Lockfile, dir: string): Lockfile;
|
|
206
218
|
/** Dependency of a workspace package resolved differently than expected. */
|
|
@@ -298,11 +310,15 @@ interface SyncOptions {
|
|
|
298
310
|
*/
|
|
299
311
|
onManifestSync?: (sync: ManifestSync) => void;
|
|
300
312
|
}
|
|
301
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* Result of `syncNestedWorkspace`.
|
|
315
|
+
*
|
|
316
|
+
* The sync failed and restored the host files when `ownDependencyChanges` is not empty or `isInSync(check)` is false.
|
|
317
|
+
*/
|
|
302
318
|
interface SyncResult {
|
|
303
|
-
/** Own dependencies of host workspaces that the sync re-resolved
|
|
319
|
+
/** Own dependencies of host workspaces that the sync re-resolved. */
|
|
304
320
|
ownDependencyChanges: DependencyMismatch[];
|
|
305
|
-
/** Check of the synced files
|
|
321
|
+
/** Check of the synced files. */
|
|
306
322
|
check: CheckResult;
|
|
307
323
|
}
|
|
308
324
|
/**
|
|
@@ -310,6 +326,9 @@ interface SyncResult {
|
|
|
310
326
|
*
|
|
311
327
|
* Writes the synced `package.json`, writes the merged `bun.lock`, runs `bun install`,
|
|
312
328
|
* and then compares the result with the nested checkout and with the original host resolutions.
|
|
329
|
+
* When `bun install` fails or the result differs, restores both files before it throws or returns,
|
|
330
|
+
* so that the next sync starts from the original host resolutions.
|
|
331
|
+
* Throws an error that says so when it cannot restore them.
|
|
313
332
|
*/
|
|
314
333
|
export declare function syncNestedWorkspace(root: string, options?: SyncOptions): Promise<SyncResult>;
|
|
315
334
|
export type { CheckResult, DependencyMismatch, Lockfile, ManifestDifference, ManifestSync, NestedWorkspace, NestedWorkspaceConfig, OverriddenEntry, PackageManifest, PackageMismatch, ResolvedConfig, SyncOptions, SyncResult, SyncedEntries, SyncedField, WorkspacesConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as BunInstallError,
|
|
1
|
+
export { t as BunInstallError, p as applyManifestToLockfile, n as checkNestedWorkspace, v as diffManifests, c as findDirectDependencyMismatches, l as findOwnDependencyChanges, u as findTransitiveMismatches, d as findUncoveredWorkspaces, f as findWorkspaceMismatches, i as isInSync, o as loadNestedWorkspace, m as mergeLockfiles, h as parseLockfile, g as relocateLockfile, _ as supportedLockfileVersions, y as syncManifest, s as syncNestedWorkspace, b as syncedFields } from "./workspace-CC5KeN8i.mjs";
|