@webjsdev/server 0.8.36 → 0.8.38
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 +53 -3
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
|
|
@@ -66,6 +67,18 @@ import { BUFFERED_MARKER } from './conditional-get.js';
|
|
|
66
67
|
*/
|
|
67
68
|
const BUILTIN = new Set(['@webjsdev/core', '@webjsdev/core/']);
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Server-only framework packages that must NEVER be vendored to the browser.
|
|
72
|
+
* Unlike `@webjsdev/core` (browser-bound, served locally via `/__webjs/core/*`),
|
|
73
|
+
* these are pure server packages: the CLI (and its `webjs-bun.mjs` bootstrap
|
|
74
|
+
* import, #675), the SSR runtime, the MCP server. A stray browser-graph scan
|
|
75
|
+
* that surfaces one of them must not push it onto the jspm path (#713). Matched
|
|
76
|
+
* on the extracted package name, so subpaths (`@webjsdev/cli/bin/webjs.js`) are
|
|
77
|
+
* covered. `@webjsdev/ui` is intentionally absent: its components ARE
|
|
78
|
+
* browser-bound, so it stays vendorable.
|
|
79
|
+
*/
|
|
80
|
+
const FRAMEWORK_SERVER_ONLY = new Set(['@webjsdev/cli', '@webjsdev/server', '@webjsdev/mcp']);
|
|
81
|
+
|
|
69
82
|
/**
|
|
70
83
|
* Scan source files under `dir` for bare import specifiers reachable
|
|
71
84
|
* from the browser. Returns a Set of package names.
|
|
@@ -90,6 +103,12 @@ export async function scanBareImports(dir, skipFiles) {
|
|
|
90
103
|
const found = new Set();
|
|
91
104
|
await walk(dir, found, skipFiles);
|
|
92
105
|
for (const b of BUILTIN) found.delete(b);
|
|
106
|
+
// Drop server-only framework packages (and their subpaths) so they never
|
|
107
|
+
// reach the importmap / jspm path (#713).
|
|
108
|
+
for (const spec of found) {
|
|
109
|
+
const p = extractPackageName(spec);
|
|
110
|
+
if (p && FRAMEWORK_SERVER_ONLY.has(p)) found.delete(spec);
|
|
111
|
+
}
|
|
93
112
|
return found;
|
|
94
113
|
}
|
|
95
114
|
|
|
@@ -139,6 +158,10 @@ function isServerOnlyFile(name) {
|
|
|
139
158
|
if (/\.server\.(js|ts|mjs|mts)$/.test(name)) return true;
|
|
140
159
|
if (/^route\.(js|ts|mjs|mts)$/.test(name)) return true;
|
|
141
160
|
if (/^middleware\.(js|ts|mjs|mts)$/.test(name)) return true;
|
|
161
|
+
// The zero-install Bun bootstrap (#675): a server-only entry that imports
|
|
162
|
+
// `@webjsdev/cli`. It never loads in the browser, so keep it out of the
|
|
163
|
+
// vendor scan, else the CLI would be pushed onto the jspm path (#713).
|
|
164
|
+
if (name === 'webjs-bun.mjs') return true;
|
|
142
165
|
return false;
|
|
143
166
|
}
|
|
144
167
|
|
|
@@ -261,6 +284,26 @@ export function getPackageVersion(pkgName, appDir) {
|
|
|
261
284
|
}
|
|
262
285
|
}
|
|
263
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Declared dep versions for the zero-install importmap fallback (#699): the
|
|
289
|
+
* SAME source the Bun server pin uses (`resolveDepVersions`), so the browser
|
|
290
|
+
* importmap and the server resolve a vendor from one source. Returns a map of
|
|
291
|
+
* package name to the bun.lock exact (when present) else the package.json
|
|
292
|
+
* declared semver (an exact or an inline-safe range). Covers `dependencies` and
|
|
293
|
+
* `devDependencies` (what `resolveDepVersions` reads), NOT `peerDependencies`.
|
|
294
|
+
* Empty when there is no readable package.json. Used only as a fallback when
|
|
295
|
+
* `getPackageVersion` (require.resolve against node_modules) finds nothing.
|
|
296
|
+
* @param {string} appDir
|
|
297
|
+
* @returns {Record<string, string>}
|
|
298
|
+
*/
|
|
299
|
+
export function declaredVendorVersions(appDir) {
|
|
300
|
+
let pkgText;
|
|
301
|
+
try { pkgText = readFileSync(join(appDir, 'package.json'), 'utf8'); } catch { return {}; }
|
|
302
|
+
let lockText = null;
|
|
303
|
+
try { lockText = readFileSync(join(appDir, 'bun.lock'), 'utf8'); } catch { /* optional */ }
|
|
304
|
+
return resolveDepVersions(pkgText, lockText);
|
|
305
|
+
}
|
|
306
|
+
|
|
264
307
|
/**
|
|
265
308
|
* Read the installed package's declared `dependencies` + `peerDependencies`
|
|
266
309
|
* from its `package.json`, hoist-aware (same resolution as `getPackageVersion`,
|
|
@@ -636,11 +679,18 @@ function mergePerInstall(fragments) {
|
|
|
636
679
|
*/
|
|
637
680
|
export async function vendorImportMapEntries(bareImports, appDir) {
|
|
638
681
|
const installs = [];
|
|
682
|
+
// Zero-install fallback (#699). When node_modules is absent (Bun
|
|
683
|
+
// zero-install), `getPackageVersion` (require.resolve) finds nothing, so the
|
|
684
|
+
// entry would be dropped and the browser bare import would 404. Fall back to
|
|
685
|
+
// the SAME source the Bun server pin uses, so the importmap and the server
|
|
686
|
+
// resolve a vendor from one source: bun.lock exact when present, else the
|
|
687
|
+
// package.json declared semver (jspm resolves a range, so a range works).
|
|
688
|
+
const declared = declaredVendorVersions(appDir);
|
|
639
689
|
for (const spec of bareImports) {
|
|
640
690
|
if (BUILTIN.has(spec)) continue;
|
|
641
691
|
const pkg = extractPackageName(spec);
|
|
642
|
-
if (!pkg || BUILTIN.has(pkg)) continue;
|
|
643
|
-
const version = getPackageVersion(pkg, appDir);
|
|
692
|
+
if (!pkg || BUILTIN.has(pkg) || FRAMEWORK_SERVER_ONLY.has(pkg)) continue;
|
|
693
|
+
const version = getPackageVersion(pkg, appDir) || declared[pkg];
|
|
644
694
|
if (!version) continue;
|
|
645
695
|
// Splice the version into the specifier: 'dayjs/plugin/utc' with
|
|
646
696
|
// version 1.11.13 becomes 'dayjs@1.11.13/plugin/utc'. jspm.io's
|
|
@@ -1208,7 +1258,7 @@ export async function pinAll(appDir, opts = {}) {
|
|
|
1208
1258
|
for (const spec of bare) {
|
|
1209
1259
|
if (BUILTIN.has(spec)) continue;
|
|
1210
1260
|
const pkg = extractPackageName(spec);
|
|
1211
|
-
if (!pkg || BUILTIN.has(pkg)) continue;
|
|
1261
|
+
if (!pkg || BUILTIN.has(pkg) || FRAMEWORK_SERVER_ONLY.has(pkg)) continue;
|
|
1212
1262
|
const version = getPackageVersion(pkg, appDir);
|
|
1213
1263
|
if (!version) continue;
|
|
1214
1264
|
const subpath = spec.slice(pkg.length);
|