bun-nested-workspace 0.1.0 → 0.2.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 +22 -9
- package/dist/cli.mjs +13 -3
- package/dist/index.d.mts +16 -2
- package/dist/index.mjs +1 -1
- package/dist/{workspace-DJ2dh3q7.mjs → workspace-DOgB-vkj.mjs} +21 -8
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
COPY
|
|
64
|
-
RUN
|
|
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
|
|
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-
|
|
2
|
+
import { a as syncNestedWorkspace, i as loadNestedWorkspace, n as checkNestedWorkspace, r as isInSync, t as BunInstallError, y as getErrorMessage } from "./workspace-DOgB-vkj.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 {
|
|
47
|
-
|
|
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-
|
|
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-DOgB-vkj.mjs";
|
|
@@ -27,13 +27,21 @@ function parseResolvedSpec(spec) {
|
|
|
27
27
|
version
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
/** Error thrown when `bun.lock` has a structure that the tools do not support. */
|
|
31
|
+
var BunLockFormatError = class extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "BunLockFormatError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/** Parses `bun.lock` text and throws `BunLockFormatError` on unsupported structure. */
|
|
30
38
|
function parseBunLock(lockText) {
|
|
31
39
|
const parsed = JSON5.parse(lockText);
|
|
32
|
-
if (!isObject(parsed)) throw new
|
|
40
|
+
if (!isObject(parsed)) throw new BunLockFormatError("bun.lock must parse to an object");
|
|
33
41
|
const overrides = parsed.overrides;
|
|
34
42
|
if (overrides !== void 0) {
|
|
35
|
-
if (!isObject(overrides) || Array.isArray(overrides)) throw new
|
|
36
|
-
for (const [name, value] of Object.entries(overrides)) if (typeof value !== "string") throw new
|
|
43
|
+
if (!isObject(overrides) || Array.isArray(overrides)) throw new BunLockFormatError("bun.lock overrides must be an object");
|
|
44
|
+
for (const [name, value] of Object.entries(overrides)) if (typeof value !== "string") throw new BunLockFormatError(`bun.lock override ${JSON.stringify(name)} is scoped to a parent package or a version selector; only overrides by package name are supported`);
|
|
37
45
|
}
|
|
38
46
|
return parsed;
|
|
39
47
|
}
|
|
@@ -41,7 +49,8 @@ function parseBunLock(lockText) {
|
|
|
41
49
|
/**
|
|
42
50
|
* Read the exact bare-name override Bun stores for a dependency.
|
|
43
51
|
*
|
|
44
|
-
*
|
|
52
|
+
* `parseBunLock` rejects overrides scoped to a parent package or a version selector.
|
|
53
|
+
* Bun versions before 1.4 keep selector keys as strings without applying them,
|
|
45
54
|
* so matching those here would diverge from the package manager.
|
|
46
55
|
*/
|
|
47
56
|
function dependencyOverrideRange(lock, dependencyName) {
|
|
@@ -245,7 +254,8 @@ function setCatalogs(manifest, catalogs) {
|
|
|
245
254
|
*
|
|
246
255
|
* A nested entry replaces a host entry with the same name, while the other host entries stay.
|
|
247
256
|
* 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
|
|
257
|
+
* Throws when the host and the nested repository patch the same package version,
|
|
258
|
+
* and when either side defines `resolutions` while `overrides` are synced.
|
|
249
259
|
*/
|
|
250
260
|
function syncManifest(host, nested) {
|
|
251
261
|
const config = readConfig(host);
|
|
@@ -280,6 +290,7 @@ function syncManifest(host, nested) {
|
|
|
280
290
|
}
|
|
281
291
|
manifest = setCatalogs(manifest, catalogs);
|
|
282
292
|
if (config.fields.includes("overrides")) {
|
|
293
|
+
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
294
|
const merged = mergeEntries("overrides", host.overrides, nested.overrides, synced.overrides);
|
|
284
295
|
manifest.overrides = merged.entries;
|
|
285
296
|
synced.overrides = merged.synced;
|
|
@@ -304,7 +315,8 @@ function syncManifest(host, nested) {
|
|
|
304
315
|
} : rest;
|
|
305
316
|
return {
|
|
306
317
|
manifest,
|
|
307
|
-
overridden
|
|
318
|
+
overridden,
|
|
319
|
+
addedTrustedDependencies: host.trustedDependencies === void 0 && manifest.trustedDependencies !== void 0
|
|
308
320
|
};
|
|
309
321
|
}
|
|
310
322
|
/**
|
|
@@ -800,15 +812,16 @@ async function runBunInstall(root, args = []) {
|
|
|
800
812
|
async function syncNestedWorkspace(root, options = {}) {
|
|
801
813
|
const workspace = await loadNestedWorkspace(root);
|
|
802
814
|
assertWorkspacesCovered(workspace);
|
|
803
|
-
const
|
|
815
|
+
const manifestSync = syncManifest(workspace.manifest, workspace.nestedManifest);
|
|
816
|
+
const { manifest } = manifestSync;
|
|
804
817
|
const manifestText = formatJson(manifest, workspace.manifestText);
|
|
805
818
|
if (manifestText !== workspace.manifestText) await Bun.write(path.join(root, "package.json"), manifestText);
|
|
819
|
+
options.onManifestSync?.(manifestSync);
|
|
806
820
|
const merged = mergeLockfiles(applyManifestToLockfile(workspace.lock, manifest), workspace.nestedLock, workspace.config.dir);
|
|
807
821
|
await Bun.write(path.join(root, "bun.lock"), renderBunLock(merged));
|
|
808
822
|
await runBunInstall(root, options.installArgs);
|
|
809
823
|
const synced = await loadNestedWorkspace(root);
|
|
810
824
|
return {
|
|
811
|
-
overridden,
|
|
812
825
|
ownDependencyChanges: findOwnDependencyChanges(workspace.lock, synced.lock, workspace.nestedLock, workspace.config.dir),
|
|
813
826
|
check: checkNestedWorkspace(synced)
|
|
814
827
|
};
|
package/package.json
CHANGED