@webjsdev/server 0.8.36 → 0.8.37
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/package.json +1 -1
- package/src/action-seed.js +9 -2
- package/src/bun-pin-rewrite.js +51 -11
- package/src/vendor.js +29 -1
package/package.json
CHANGED
package/src/action-seed.js
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
import * as nodeModule from 'node:module';
|
|
52
52
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
53
|
-
import { readFileSync } from 'node:fs';
|
|
53
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
54
54
|
import { join } from 'node:path';
|
|
55
55
|
import { fileURLToPath } from 'node:url';
|
|
56
56
|
import { stringify } from '@webjsdev/core';
|
|
@@ -307,7 +307,14 @@ function seedLoadHook(url, context, nextLoad) {
|
|
|
307
307
|
* @param {string} appDir
|
|
308
308
|
* @returns {((src: string, loader: 'ts' | 'js') => string) | null}
|
|
309
309
|
*/
|
|
310
|
-
function buildBunPinTransform(appDir) {
|
|
310
|
+
export function buildBunPinTransform(appDir) {
|
|
311
|
+
// Pinning is for TRUE zero-install only. When `node_modules` exists (an
|
|
312
|
+
// installed app, or a workspace member like this repo's own examples), Bun
|
|
313
|
+
// resolves bare specifiers from it, so injecting an inline version would
|
|
314
|
+
// bypass that. Worse, for a workspace-linked dep it would swap the local
|
|
315
|
+
// package for the published one (the #698 blog-on-Bun regression). So skip
|
|
316
|
+
// pinning entirely when node_modules is present; Bun uses the installed copy.
|
|
317
|
+
if (existsSync(join(appDir, 'node_modules'))) return null;
|
|
311
318
|
let pkgText;
|
|
312
319
|
try { pkgText = readFileSync(join(appDir, 'package.json'), 'utf8'); } catch { return null; }
|
|
313
320
|
let lockText = null;
|
package/src/bun-pin-rewrite.js
CHANGED
|
@@ -22,10 +22,19 @@
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Resolve the version to pin each DECLARED dependency to: the exact version from
|
|
25
|
-
* `bun.lock` when present (precise), else the package.json
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* `bun.lock` when present (precise and reproducible), else the package.json
|
|
26
|
+
* declared value passed through as-is when it is an inline-safe semver. Bun's
|
|
27
|
+
* inline specifier resolves a range the standard way (`zod@^3.20.0` picks the
|
|
28
|
+
* highest matching `3.x`, verified), so passing the declared range through is
|
|
29
|
+
* the correct semver behaviour, the same a fresh `bun install` would pick. Only
|
|
30
|
+
* declared deps are returned, so the rewrite never pins a transitive dep through
|
|
31
|
+
* an app import (those follow from the pinned direct deps' own manifests).
|
|
32
|
+
*
|
|
33
|
+
* A protocol range (`workspace:`, `file:`, `link:`, git / URL) and a bare
|
|
34
|
+
* wildcard (`*`, `x`, empty) are NOT valid inline specifiers, so they are left
|
|
35
|
+
* BARE (resolving to latest, exactly as before this feature, never to a broken
|
|
36
|
+
* specifier). For reproducibility across machines, commit a `bun.lock` (its
|
|
37
|
+
* exact pin then wins over a floating range).
|
|
29
38
|
*
|
|
30
39
|
* Runtime-neutral: takes the two file contents (the Bun glue reads them via
|
|
31
40
|
* `Bun.file`), so this stays unit-testable on Node.
|
|
@@ -55,14 +64,14 @@ export function resolveDepVersions(pkgJsonText, bunLockText) {
|
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
for (const [name, range] of Object.entries(declared)) {
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// `
|
|
62
|
-
//
|
|
63
|
-
//
|
|
67
|
+
// bun.lock exact wins (precise and reproducible). Otherwise pass the
|
|
68
|
+
// declared semver through as an inline specifier: Bun resolves an exact,
|
|
69
|
+
// caret, tilde, or comparator range the standard way (highest match), so
|
|
70
|
+
// `name@^1.2.3` is correct, not broken. A protocol range (`workspace:`,
|
|
71
|
+
// `file:`, git / URL) and a bare wildcard (`*`, `x`, empty) are NOT
|
|
72
|
+
// inline-safe, so they are left BARE (latest, as before).
|
|
64
73
|
if (lockExact[name]) out[name] = lockExact[name];
|
|
65
|
-
else if (
|
|
74
|
+
else if (isInlineableVersion(range)) out[name] = range;
|
|
66
75
|
}
|
|
67
76
|
return out;
|
|
68
77
|
}
|
|
@@ -79,6 +88,37 @@ function isExactVersion(v) {
|
|
|
79
88
|
return typeof v === 'string' && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(v);
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Whether a declared package.json version is safe to forward verbatim as a Bun
|
|
93
|
+
* inline specifier (`name@<v>`). Accepts a single-token semver: an exact version
|
|
94
|
+
* (with an optional prerelease / build suffix, `1.2.3`, `1.0.0-rc.3`) or a caret
|
|
95
|
+
* / tilde / comparator range over a numeric core WITHOUT a suffix (`^1.2.3`,
|
|
96
|
+
* `~1.2`, `>=1.2.3`, `^3`). Bun resolves these the standard way (highest match)
|
|
97
|
+
* at auto-install time.
|
|
98
|
+
*
|
|
99
|
+
* Rejects, so they are left BARE: a RANGE OPERATOR combined with a prerelease /
|
|
100
|
+
* build suffix (`^1.0.0-rc.3`, `~1.0.0-beta.1`, #703): Bun zero-install ENOENTs
|
|
101
|
+
* on a caret-prerelease inline specifier (verified, `drizzle-orm@^1.0.0-rc.3`
|
|
102
|
+
* errors while the exact `drizzle-orm@1.0.0-rc.3` resolves). Also a protocol
|
|
103
|
+
* range (`workspace:`, `file:`, `link:`, `git+...`, an `http(s)://` URL, any
|
|
104
|
+
* value with a `:`), a bare wildcard (`*`, `x`, `X`, empty), a multi-token range
|
|
105
|
+
* (a space, a `||` union, a hyphen `1 - 2` range, which would break the
|
|
106
|
+
* specifier string), and a dist-tag (`latest`, `next`, which auto-install
|
|
107
|
+
* resolves unreliably). A rejected value resolves to latest, the pre-feature
|
|
108
|
+
* behaviour, never a broken specifier.
|
|
109
|
+
* @param {unknown} v
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
function isInlineableVersion(v) {
|
|
113
|
+
if (typeof v !== 'string') return false;
|
|
114
|
+
const m = /^(>=|<=|>|<|=|\^|~)?(\d+(?:\.\d+){0,2})([-+][0-9A-Za-z.-]+)?$/.exec(v);
|
|
115
|
+
if (!m) return false;
|
|
116
|
+
// A range operator (group 1) plus a prerelease / build suffix (group 3) is not
|
|
117
|
+
// inline-resolvable under Bun zero-install (#703). An exact version with a
|
|
118
|
+
// suffix, or a range with no suffix, is fine.
|
|
119
|
+
return !(m[1] && m[3]);
|
|
120
|
+
}
|
|
121
|
+
|
|
82
122
|
/**
|
|
83
123
|
* The npm package name a specifier belongs to: `@scope/name[/sub]` or
|
|
84
124
|
* `name[/sub]`. Returns null for a bare scope with no name.
|
package/src/vendor.js
CHANGED
|
@@ -47,6 +47,7 @@ import { createRequire } from 'node:module';
|
|
|
47
47
|
import { spawnSync } from 'node:child_process';
|
|
48
48
|
import { digestBase64 } from './crypto-utils.js';
|
|
49
49
|
import { BUFFERED_MARKER } from './conditional-get.js';
|
|
50
|
+
import { resolveDepVersions } from './bun-pin-rewrite.js';
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* Set of package names whose importmap entries are populated by the
|
|
@@ -261,6 +262,26 @@ export function getPackageVersion(pkgName, appDir) {
|
|
|
261
262
|
}
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Declared dep versions for the zero-install importmap fallback (#699): the
|
|
267
|
+
* SAME source the Bun server pin uses (`resolveDepVersions`), so the browser
|
|
268
|
+
* importmap and the server resolve a vendor from one source. Returns a map of
|
|
269
|
+
* package name to the bun.lock exact (when present) else the package.json
|
|
270
|
+
* declared semver (an exact or an inline-safe range). Covers `dependencies` and
|
|
271
|
+
* `devDependencies` (what `resolveDepVersions` reads), NOT `peerDependencies`.
|
|
272
|
+
* Empty when there is no readable package.json. Used only as a fallback when
|
|
273
|
+
* `getPackageVersion` (require.resolve against node_modules) finds nothing.
|
|
274
|
+
* @param {string} appDir
|
|
275
|
+
* @returns {Record<string, string>}
|
|
276
|
+
*/
|
|
277
|
+
export function declaredVendorVersions(appDir) {
|
|
278
|
+
let pkgText;
|
|
279
|
+
try { pkgText = readFileSync(join(appDir, 'package.json'), 'utf8'); } catch { return {}; }
|
|
280
|
+
let lockText = null;
|
|
281
|
+
try { lockText = readFileSync(join(appDir, 'bun.lock'), 'utf8'); } catch { /* optional */ }
|
|
282
|
+
return resolveDepVersions(pkgText, lockText);
|
|
283
|
+
}
|
|
284
|
+
|
|
264
285
|
/**
|
|
265
286
|
* Read the installed package's declared `dependencies` + `peerDependencies`
|
|
266
287
|
* from its `package.json`, hoist-aware (same resolution as `getPackageVersion`,
|
|
@@ -636,11 +657,18 @@ function mergePerInstall(fragments) {
|
|
|
636
657
|
*/
|
|
637
658
|
export async function vendorImportMapEntries(bareImports, appDir) {
|
|
638
659
|
const installs = [];
|
|
660
|
+
// Zero-install fallback (#699). When node_modules is absent (Bun
|
|
661
|
+
// zero-install), `getPackageVersion` (require.resolve) finds nothing, so the
|
|
662
|
+
// entry would be dropped and the browser bare import would 404. Fall back to
|
|
663
|
+
// the SAME source the Bun server pin uses, so the importmap and the server
|
|
664
|
+
// resolve a vendor from one source: bun.lock exact when present, else the
|
|
665
|
+
// package.json declared semver (jspm resolves a range, so a range works).
|
|
666
|
+
const declared = declaredVendorVersions(appDir);
|
|
639
667
|
for (const spec of bareImports) {
|
|
640
668
|
if (BUILTIN.has(spec)) continue;
|
|
641
669
|
const pkg = extractPackageName(spec);
|
|
642
670
|
if (!pkg || BUILTIN.has(pkg)) continue;
|
|
643
|
-
const version = getPackageVersion(pkg, appDir);
|
|
671
|
+
const version = getPackageVersion(pkg, appDir) || declared[pkg];
|
|
644
672
|
if (!version) continue;
|
|
645
673
|
// Splice the version into the specifier: 'dayjs/plugin/utc' with
|
|
646
674
|
// version 1.11.13 becomes 'dayjs@1.11.13/plugin/utc'. jspm.io's
|