@somewhere-tech/cli 0.30.1 → 0.30.2
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/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/VENDOR.json +4 -4
- package/runtime/browser-probes.mjs +1 -1
- package/runtime/compiler/VENDOR.json +2 -2
- package/runtime/compiler/compile-core.cjs +163 -11
- package/runtime/platform-context.mjs +33 -1
- package/runtime/sw-init.mjs +1 -1
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@somewhere-tech/cli",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@somewhere-tech/cli",
|
|
9
|
-
"version": "0.30.
|
|
9
|
+
"version": "0.30.2",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"@modelcontextprotocol/sdk",
|
|
12
12
|
"chokidar",
|
package/package.json
CHANGED
package/runtime/VENDOR.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "526d2b4d",
|
|
3
3
|
"files": {
|
|
4
|
-
"sw-init.mjs": "
|
|
5
|
-
"platform-context.mjs": "
|
|
6
|
-
"browser-probes.mjs": "
|
|
4
|
+
"sw-init.mjs": "919ee7188bb6e878bfb082e953154e5991360d935535729f7856d795cf59884f",
|
|
5
|
+
"platform-context.mjs": "8709d7bf8860ab8ac8e8e3a84bbca85261e39405d222958fa7c8afd39b467383",
|
|
6
|
+
"browser-probes.mjs": "cc8856fc970287a698ae4c5ecfff434a0aad67fe61d353bc87c9630952f67ce3"
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// VENDORED from worker/src/utils/browser-test.ts (DOM_OUTLINE_SCRIPT) @
|
|
1
|
+
// VENDORED from worker/src/utils/browser-test.ts (DOM_OUTLINE_SCRIPT) @ 526d2b4d
|
|
2
2
|
// — the exact runtime deployed functions run against. Do not edit by hand;
|
|
3
3
|
// re-sync with: node scripts/extract-runtime.mjs <monorepo>
|
|
4
4
|
export const MAX_OUTLINE_NODES = 200;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "526d2b4d",
|
|
3
3
|
"esbuild": "0.24.0",
|
|
4
4
|
"toolchain": {
|
|
5
5
|
"base": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"files": {
|
|
60
|
-
"compile-core.cjs": "
|
|
60
|
+
"compile-core.cjs": "4117fcbf26ac4e06b1ab3492802a4097dd250f3ea1ec9ef5214327defd3663fc",
|
|
61
61
|
"graph-contract.cjs": "aaeebc34f486ee5f639444de5757accfc283de641e631908ccd8d8c688bfb5c0",
|
|
62
62
|
"typed-functions.cjs": "5c0b499015dcdf755ca72dab04a80afce0891e782f835547592faec802c63be6"
|
|
63
63
|
}
|
|
@@ -425,6 +425,139 @@ function cleanRange(range) {
|
|
|
425
425
|
return String(range || '').replace(/^[\^~>=<v\s]+/, '').split(/\s/)[0] || 'latest';
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
/* ─── Lockfile-derived install pins (tsk_f79d71ce) ────────────────────────────
|
|
429
|
+
* THE INSTALL SPEC IS OWNED HERE, by the core, so `somewhere dev` and
|
|
430
|
+
* `somewhere deploy` derive it from the same code and cannot disagree.
|
|
431
|
+
*
|
|
432
|
+
* The bug this closes: deploy floor-pinned the declared range (`^7.9.5` → 7.9.5)
|
|
433
|
+
* while the developer's own machine had 7.18.3 installed from their lockfile, so
|
|
434
|
+
* the local and deployed entry chunks were built against different library code
|
|
435
|
+
* — measured on the 0.30.0 journey scaffold, where react-router was the only
|
|
436
|
+
* package left in the diff. "Same app, same data, same build" was not true.
|
|
437
|
+
*
|
|
438
|
+
* THE RULE, both directions:
|
|
439
|
+
* - LOCKFILE PRESENT (npm-shrinkwrap.json, else package-lock.json — npm's own
|
|
440
|
+
* precedence): a dependency installs at the version the lockfile pins, which
|
|
441
|
+
* is exactly what the developer has locally. The pin is only honoured when it
|
|
442
|
+
* PROVABLY SATISFIES the range declared in package.json, so a lockfile can
|
|
443
|
+
* only ever move the build WITHIN what package.json already allows; an
|
|
444
|
+
* out-of-sync lockfile falls back to the floor-pin rather than installing
|
|
445
|
+
* something package.json contradicts.
|
|
446
|
+
* - NO LOCKFILE: unchanged, and this is the documented rule — third-party deps
|
|
447
|
+
* FLOOR-PIN the declared range (`^7.9.5` → 7.9.5), first-party
|
|
448
|
+
* `@somewhere-tech/*` pass the range through verbatim so npm resolves the
|
|
449
|
+
* latest satisfying version. A project with no lockfile resolves today
|
|
450
|
+
* exactly as it did before this change (rule 9).
|
|
451
|
+
*
|
|
452
|
+
* WHY `@somewhere-tech/*` IGNORES THE LOCKFILE TOO: pfb_f607d6b27453. Our own
|
|
453
|
+
* scaffold + `npm install` writes a lockfile within seconds of `somewhere init`,
|
|
454
|
+
* so honouring it for first-party packages would re-freeze every scaffold on the
|
|
455
|
+
* SDK version it was born with and published SDK fixes would never reach new
|
|
456
|
+
* deploys — the exact harm the range passthrough exists to prevent. Right after
|
|
457
|
+
* an install the locked version IS the latest in range, so the two agree; they
|
|
458
|
+
* only part as the lockfile ages, and that is the case where the platform's own
|
|
459
|
+
* package must move.
|
|
460
|
+
*
|
|
461
|
+
* BOUNDARY, stated so nobody reads more into this than it does: this pins the
|
|
462
|
+
* dependencies the core decides to install — the ones declared in the project's
|
|
463
|
+
* package.json. Their TRANSITIVE dependencies are still resolved by npm from the
|
|
464
|
+
* parents' ranges. Pinning the whole locked closure was considered and rejected
|
|
465
|
+
* for now: an explicit exact spec for every transitive can trip ERESOLVE peer
|
|
466
|
+
* conflicts and platform-specific optional binaries, which would turn a build
|
|
467
|
+
* that works today into a refused one (rule 9 — tighten incrementally). If a
|
|
468
|
+
* transitive drift is ever measured, that is the next step, not a redesign.
|
|
469
|
+
*
|
|
470
|
+
* Prefer-baked resolution is deliberately NOT changed: which packages come from
|
|
471
|
+
* the baked image, and the React-19 symlink hot path, still key off the DECLARED
|
|
472
|
+
* RANGE. Making the baked decision exact-match would cold-install React on every
|
|
473
|
+
* lockfile project (tsk_2553662f's 600s timeout) and would re-open the very
|
|
474
|
+
* divergence tsk_0312cf17 closed by pinning the local loop to the image's own
|
|
475
|
+
* React set. Aligning the local loop with the rest of the baked set is the CLI's
|
|
476
|
+
* half of this, not the compiler's.
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
/** npm's own lockfile precedence: a shrinkwrap wins over a package-lock. */
|
|
480
|
+
const LOCKFILE_NAMES = ['npm-shrinkwrap.json', 'package-lock.json'];
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Versions an npm lockfile pins for the project's TOP-LEVEL packages.
|
|
484
|
+
*
|
|
485
|
+
* Only `node_modules/<name>` entries count: a nested `node_modules/a/node_modules/b`
|
|
486
|
+
* path is a transitive's private copy, not the version the project itself
|
|
487
|
+
* resolves. Link entries (workspaces, `file:` deps) carry no registry version and
|
|
488
|
+
* are skipped. Handles lockfileVersion 2/3 (`packages`) and 1 (`dependencies`).
|
|
489
|
+
* Malformed input yields an empty map — never a throw; a lockfile we cannot read
|
|
490
|
+
* degrades to today's floor-pin.
|
|
491
|
+
*/
|
|
492
|
+
function lockfileVersions(text) {
|
|
493
|
+
const out = new Map();
|
|
494
|
+
if (typeof text !== 'string' || !text.trim()) return out;
|
|
495
|
+
let lock;
|
|
496
|
+
try { lock = JSON.parse(text); } catch { return out; }
|
|
497
|
+
if (!lock || typeof lock !== 'object') return out;
|
|
498
|
+
const packages = lock.packages && typeof lock.packages === 'object' ? lock.packages : null;
|
|
499
|
+
if (packages) {
|
|
500
|
+
for (const [entryPath, entry] of Object.entries(packages)) {
|
|
501
|
+
if (!entryPath.startsWith('node_modules/')) continue;
|
|
502
|
+
const name = entryPath.slice('node_modules/'.length);
|
|
503
|
+
if (name.includes('/node_modules/')) continue;
|
|
504
|
+
if (!entry || typeof entry !== 'object' || entry.link) continue;
|
|
505
|
+
if (typeof entry.version !== 'string' || !entry.version) continue;
|
|
506
|
+
out.set(name, entry.version);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const v1 = lock.dependencies && typeof lock.dependencies === 'object' ? lock.dependencies : null;
|
|
510
|
+
if (v1) {
|
|
511
|
+
for (const [name, entry] of Object.entries(v1)) {
|
|
512
|
+
if (out.has(name)) continue;
|
|
513
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
514
|
+
if (typeof entry.version !== 'string' || !entry.version) continue;
|
|
515
|
+
if (/^(?:file|link|git\+|https?):/.test(entry.version)) continue;
|
|
516
|
+
out.set(name, entry.version);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* The one derivation of what to hand `npm install` for a single dependency.
|
|
524
|
+
* PURE — `satisfies(version, range)` is passed in (semver is host-loaded) and
|
|
525
|
+
* must return TRUE only when it can prove the version is inside the range;
|
|
526
|
+
* anything it cannot prove falls back to the floor-pin, i.e. today's behavior.
|
|
527
|
+
*
|
|
528
|
+
* Exported so the fixture can pin the rule in BOTH directions without a build.
|
|
529
|
+
*/
|
|
530
|
+
function resolveInstallSpec({ name, range, lockedVersion, satisfies }) {
|
|
531
|
+
// First-party platform packages: declared range verbatim, lockfile ignored.
|
|
532
|
+
// See the block comment above (pfb_f607d6b27453).
|
|
533
|
+
if (name.startsWith('@somewhere-tech/')) {
|
|
534
|
+
return `${name}@${String(range || 'latest').trim() || 'latest'}`;
|
|
535
|
+
}
|
|
536
|
+
if (lockedVersion && typeof satisfies === 'function') {
|
|
537
|
+
let inRange = false;
|
|
538
|
+
try { inRange = satisfies(lockedVersion, range) === true; } catch { inRange = false; }
|
|
539
|
+
if (inRange) return `${name}@${lockedVersion}`;
|
|
540
|
+
}
|
|
541
|
+
return `${name}@${cleanRange(range)}`;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* The project's locked versions, read from the materialized build root. Both
|
|
546
|
+
* hosts land here: the container materializes every uploaded file (the lockfile
|
|
547
|
+
* included) into the build root, and the CLI's build root is the project itself.
|
|
548
|
+
* A missing or unreadable lockfile is not an error — it is the documented
|
|
549
|
+
* no-lockfile path.
|
|
550
|
+
*/
|
|
551
|
+
function readLockedVersions(root) {
|
|
552
|
+
for (const name of LOCKFILE_NAMES) {
|
|
553
|
+
let text;
|
|
554
|
+
try { text = fs.readFileSync(path.join(root, name), 'utf8'); } catch { continue; }
|
|
555
|
+
const versions = lockfileVersions(text);
|
|
556
|
+
if (versions.size) return versions;
|
|
557
|
+
}
|
|
558
|
+
return new Map();
|
|
559
|
+
}
|
|
560
|
+
|
|
428
561
|
/**
|
|
429
562
|
* Is `name` resolvable the way ESBUILD will resolve it — i.e. as a direct child
|
|
430
563
|
* package of one of the exact search dirs (the per-build install + the baked
|
|
@@ -851,6 +984,20 @@ function createCompileCore(host) {
|
|
|
851
984
|
async function ensureDeps(root, pkg, warnings, ctx, scopedProxyReady = true) {
|
|
852
985
|
const deps = (pkg && pkg.dependencies) || {};
|
|
853
986
|
const buildRoot = path.join(root, 'node_modules');
|
|
987
|
+
// The project's own lockfile, materialized alongside package.json. It IS
|
|
988
|
+
// uploaded on every deploy and was, until now, ignored — which is what made
|
|
989
|
+
// the deployed build resolve a different version of an app dependency than
|
|
990
|
+
// the developer's machine (tsk_f79d71ce). Missing/unreadable → empty map →
|
|
991
|
+
// the floor-pin path, unchanged.
|
|
992
|
+
const locked = readLockedVersions(root);
|
|
993
|
+
const semverForLock = getSemver();
|
|
994
|
+
const satisfiesLocked = semverForLock
|
|
995
|
+
? (version, range) => {
|
|
996
|
+
const wanted = (typeof range === 'string' && range.trim()) ? range.trim() : '*';
|
|
997
|
+
if (semverForLock.validRange(wanted) === null) return false;
|
|
998
|
+
return semverForLock.satisfies(version, wanted, { includePrerelease: true });
|
|
999
|
+
}
|
|
1000
|
+
: null;
|
|
854
1001
|
const missing = [];
|
|
855
1002
|
// react/react-dom whose pin the baked 18 misses but the baked 19 satisfies:
|
|
856
1003
|
// collect them to symlink from the isolated react19 set instead of installing.
|
|
@@ -864,16 +1011,13 @@ function createCompileCore(host) {
|
|
|
864
1011
|
if (isResolvable(name, IMAGE_NODE_MODULES) && bakedSatisfies(name, range)) continue;
|
|
865
1012
|
const r19 = react19BakedDir(name, range);
|
|
866
1013
|
if (r19) { react19Links.push({ name, dir: r19 }); continue; }
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
? `${name}@${String(range || 'latest').trim() || 'latest'}`
|
|
875
|
-
: `${name}@${cleanRange(range)}`;
|
|
876
|
-
missing.push(spec);
|
|
1014
|
+
// WHAT version to install is resolveInstallSpec's single decision — the
|
|
1015
|
+
// project's lockfile when it pins one inside the declared range, the
|
|
1016
|
+
// floor-pin otherwise, the declared range verbatim for first-party
|
|
1017
|
+
// @somewhere-tech/* packages. See the block comment on resolveInstallSpec.
|
|
1018
|
+
missing.push(resolveInstallSpec({
|
|
1019
|
+
name, range, lockedVersion: locked.get(name), satisfies: satisfiesLocked,
|
|
1020
|
+
}));
|
|
877
1021
|
}
|
|
878
1022
|
// Symlink the baked React-19 packages into the build root so esbuild resolves
|
|
879
1023
|
// them from root/node_modules (which it consults before the baked nodePaths) —
|
|
@@ -888,7 +1032,9 @@ function createCompileCore(host) {
|
|
|
888
1032
|
if (!fs.existsSync(link)) fs.symlinkSync(dir, link, 'dir');
|
|
889
1033
|
} catch (e) {
|
|
890
1034
|
warnings.push(`react19 link skipped for ${name}; installing per-build: ${e && e.message ? e.message : String(e)}`);
|
|
891
|
-
missing.push(
|
|
1035
|
+
missing.push(resolveInstallSpec({
|
|
1036
|
+
name, range: deps[name], lockedVersion: locked.get(name), satisfies: satisfiesLocked,
|
|
1037
|
+
}));
|
|
892
1038
|
}
|
|
893
1039
|
}
|
|
894
1040
|
}
|
|
@@ -1774,6 +1920,12 @@ function isBuildConfigFile(filePath) {
|
|
|
1774
1920
|
module.exports = {
|
|
1775
1921
|
createCompileCore,
|
|
1776
1922
|
COMPILER_CONTRACT,
|
|
1923
|
+
// Install-spec derivation (tsk_f79d71ce) — pure, exported for the fixture
|
|
1924
|
+
// that pins the rule in both directions (lockfile present / absent).
|
|
1925
|
+
LOCKFILE_NAMES,
|
|
1926
|
+
lockfileVersions,
|
|
1927
|
+
resolveInstallSpec,
|
|
1928
|
+
cleanRange,
|
|
1777
1929
|
NODE_POLYFILLS,
|
|
1778
1930
|
ASSET_LOADERS,
|
|
1779
1931
|
KNOWN_BAD_VERSIONS,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// VENDORED from worker/src/runtime/context.ts (PLATFORM_CONTEXT_JS) @
|
|
1
|
+
// VENDORED from worker/src/runtime/context.ts (PLATFORM_CONTEXT_JS) @ 526d2b4d
|
|
2
2
|
// — the exact runtime deployed functions run against. Do not edit by hand;
|
|
3
3
|
// re-sync with: node scripts/extract-runtime.mjs <monorepo>
|
|
4
4
|
// ── Primordial freeze (tsk_327fe8a4, opinionated-not-greenfield) ───────────
|
|
@@ -2702,6 +2702,19 @@ function buildPlatformContext(env, request, runtimeFetch = fetch, outboundFetch
|
|
|
2702
2702
|
'sw.db.' + verb + ' on "' + table + '": the value for "' + col.n + '" must be ' + __sw_structExpectedType(col.t) +
|
|
2703
2703
|
' (got ' + got + '). The column types are declared in db/schema.ts.');
|
|
2704
2704
|
}
|
|
2705
|
+
// The exact form for a whole number wider than a JavaScript number:
|
|
2706
|
+
// plain digits, optionally signed, exactly as a read returns them.
|
|
2707
|
+
const __SW_STRUCT_WHOLE_NUMBER_RE = /^-?\d+$/;
|
|
2708
|
+
// Compare that text against the 64-bit range without a numeric
|
|
2709
|
+
// conversion, which is the very thing that would lose it.
|
|
2710
|
+
function __sw_wholeNumberTextInRange(s) {
|
|
2711
|
+
const negative = s.charCodeAt(0) === 45;
|
|
2712
|
+
let digits = negative ? s.slice(1) : s;
|
|
2713
|
+
while (digits.length > 1 && digits.charCodeAt(0) === 48) digits = digits.slice(1);
|
|
2714
|
+
const limit = negative ? '9223372036854775808' : '9223372036854775807';
|
|
2715
|
+
if (digits.length !== limit.length) return digits.length < limit.length;
|
|
2716
|
+
return digits <= limit;
|
|
2717
|
+
}
|
|
2705
2718
|
// Type-check (and for json: serialize) ONE written value against its
|
|
2706
2719
|
// declared column. Returns the value to bind.
|
|
2707
2720
|
function __sw_structCheckWriteValue(verb, table, col, v) {
|
|
@@ -2729,6 +2742,25 @@ function buildPlatformContext(env, request, runtimeFetch = fetch, outboundFetch
|
|
|
2729
2742
|
}
|
|
2730
2743
|
if (typeof v === 'object') __sw_structThrowTypeMismatch(verb, table, col, __sw_structGotKind(v));
|
|
2731
2744
|
if (col.t === 'integer') {
|
|
2745
|
+
// A whole number wider than 9007199254740991 cannot travel as a
|
|
2746
|
+
// JavaScript number at all — the language rounds it before this
|
|
2747
|
+
// function is reached — so the exact form for those is the same
|
|
2748
|
+
// decimal STRING a read returns (tsk_d741c40b). Accepting it here is
|
|
2749
|
+
// what makes read -> write back lossless on a managed table; the
|
|
2750
|
+
// database converts it on the way in because the column is an
|
|
2751
|
+
// integer. Text that is not a whole number keeps the type error it
|
|
2752
|
+
// has always had.
|
|
2753
|
+
if (typeof v === 'string') {
|
|
2754
|
+
if (!__sw_reMatch(__SW_STRUCT_WHOLE_NUMBER_RE, v)) {
|
|
2755
|
+
__sw_structThrowTypeMismatch(verb, table, col, 'text that is not a whole number');
|
|
2756
|
+
}
|
|
2757
|
+
if (!__sw_wholeNumberTextInRange(v)) {
|
|
2758
|
+
__sw_structThrowSchema('DATABASE_VALUE_TOO_LARGE',
|
|
2759
|
+
'sw.db.' + verb + ' on "' + table + '": the value for "' + col.n + '" is outside the range of whole numbers this database stores ' +
|
|
2760
|
+
'(-9223372036854775808 to 9223372036854775807). Keep the value inside that range, or store it as text.');
|
|
2761
|
+
}
|
|
2762
|
+
return v;
|
|
2763
|
+
}
|
|
2732
2764
|
if (typeof v !== 'number' || v !== v || !Number.isInteger(v)) __sw_structThrowTypeMismatch(verb, table, col, typeof v === 'number' ? 'a number with a fractional part' : __sw_structGotKind(v));
|
|
2733
2765
|
return v;
|
|
2734
2766
|
}
|
package/runtime/sw-init.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// VENDORED from worker/src/utils/function-bundle.ts (SW_INIT_JS) @
|
|
1
|
+
// VENDORED from worker/src/utils/function-bundle.ts (SW_INIT_JS) @ 526d2b4d
|
|
2
2
|
// — the exact runtime deployed functions run against. Do not edit by hand;
|
|
3
3
|
// re-sync with: node scripts/extract-runtime.mjs <monorepo>
|
|
4
4
|
// Generated by somewhere.tech deploy pipeline. Sets up the global
|