@bongos/core 1.19.51 → 1.19.52
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/.bongos-core.json +18 -13
- package/modules/dev-box/app/src/gds-client.js +6 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/serve-internal.js +34 -21
- package/src/module-api.js +3 -1
- package/tests/devbox_downloads.mjs +17 -0
- package/tests/devbox_update_path.mjs +84 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.52",
|
|
6
|
+
"core_contract": "1.19.52",
|
|
7
|
+
"source_commit": "ce8e0d15694aed30b1640891c4256c96322a2a6f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-11T19:06:37.776Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 291,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1218,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1530,
|
|
20
|
+
"tree_sha256": "72953759b072de03029b81979095699aeb2d45b2363ba7047d8503457761956f",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -2502,7 +2502,7 @@
|
|
|
2502
2502
|
{
|
|
2503
2503
|
"path": "modules/dev-box/app/src/gds-client.js",
|
|
2504
2504
|
"mode": "0000644",
|
|
2505
|
-
"sha256": "
|
|
2505
|
+
"sha256": "323f5e71b7836dd1fb6c7710e1b2159dec6382df89a7c5d001b7e7cccd5aa46d"
|
|
2506
2506
|
},
|
|
2507
2507
|
{
|
|
2508
2508
|
"path": "modules/dev-box/app/src/main.js",
|
|
@@ -4037,12 +4037,12 @@
|
|
|
4037
4037
|
{
|
|
4038
4038
|
"path": "package-lock.json",
|
|
4039
4039
|
"mode": "0000644",
|
|
4040
|
-
"sha256": "
|
|
4040
|
+
"sha256": "37bb9183f916ffafd19c5c88408f5f776bdb091afc46b1bfbad64669d642102f"
|
|
4041
4041
|
},
|
|
4042
4042
|
{
|
|
4043
4043
|
"path": "package.json",
|
|
4044
4044
|
"mode": "0000644",
|
|
4045
|
-
"sha256": "
|
|
4045
|
+
"sha256": "80465cb4506affe5fc47c835df7a0f4f1634a0743c2de6d14f843f342d487c45"
|
|
4046
4046
|
},
|
|
4047
4047
|
{
|
|
4048
4048
|
"path": "public-docs/index.html",
|
|
@@ -5632,7 +5632,7 @@
|
|
|
5632
5632
|
{
|
|
5633
5633
|
"path": "src/bongos/serve-internal.js",
|
|
5634
5634
|
"mode": "0000644",
|
|
5635
|
-
"sha256": "
|
|
5635
|
+
"sha256": "2f88796346b1c3a6dc49e25766b223fbf2d99e34b38b9a0f1e64a6fb9a59856d"
|
|
5636
5636
|
},
|
|
5637
5637
|
{
|
|
5638
5638
|
"path": "src/bongos/uptime-poller.js",
|
|
@@ -5662,7 +5662,7 @@
|
|
|
5662
5662
|
{
|
|
5663
5663
|
"path": "src/module-api.js",
|
|
5664
5664
|
"mode": "0000644",
|
|
5665
|
-
"sha256": "
|
|
5665
|
+
"sha256": "fc779475caf64dd09a7a3f952b4dadfcf2f123ca1a3d0ea9af20762b3de5e6c8"
|
|
5666
5666
|
},
|
|
5667
5667
|
{
|
|
5668
5668
|
"path": "src/module-loader/loader.js",
|
|
@@ -6247,7 +6247,7 @@
|
|
|
6247
6247
|
{
|
|
6248
6248
|
"path": "tests/devbox_downloads.mjs",
|
|
6249
6249
|
"mode": "0000644",
|
|
6250
|
-
"sha256": "
|
|
6250
|
+
"sha256": "897fe6b114c612e132e6fa0b3c68f43b865c684c7653e73cf42db894a7e4dc94"
|
|
6251
6251
|
},
|
|
6252
6252
|
{
|
|
6253
6253
|
"path": "tests/devbox_icons.mjs",
|
|
@@ -6264,6 +6264,11 @@
|
|
|
6264
6264
|
"mode": "0000644",
|
|
6265
6265
|
"sha256": "ac0a024d96ef40af52af8b94570447ad5f1556467b68380c1b45fd3419139ad6"
|
|
6266
6266
|
},
|
|
6267
|
+
{
|
|
6268
|
+
"path": "tests/devbox_update_path.mjs",
|
|
6269
|
+
"mode": "0000644",
|
|
6270
|
+
"sha256": "b58e87eeca17053709a8e2bdcefd032060358cc85314d28bf816a92d465fdae8"
|
|
6271
|
+
},
|
|
6267
6272
|
{
|
|
6268
6273
|
"path": "tests/device_flow_start.mjs",
|
|
6269
6274
|
"mode": "0000644",
|
|
@@ -82,8 +82,12 @@ function createClient({ getToken, getApiBase, fetchImpl } = {}) {
|
|
|
82
82
|
claimableTasks: () => unwrap(gen('').request('GET', '/api/gds/tasks/claimable')),
|
|
83
83
|
claimTask: (taskId) => unwrap(gen('').request('POST', '/api/gds/claims', { body: { task_id: taskId } })),
|
|
84
84
|
boxScope: () => unwrap(gen('').request('GET', '/api/gds/box/scope')),
|
|
85
|
-
// update check (public
|
|
86
|
-
|
|
85
|
+
// update check (public route at the origin root, not under /api/gds).
|
|
86
|
+
// task 1002649: polls the EXTENSIONLESS /version twin, NOT latest.json — a
|
|
87
|
+
// front proxy with a file-matcher can eat *.json paths before Node (the
|
|
88
|
+
// live instance's pre-ADR-0152 vhost 404'd latest.json empty while this
|
|
89
|
+
// endpoint worked), and the updater only needs { version, assets } anyway.
|
|
90
|
+
latestRelease: () => unwrap(gen('').request('GET', '/downloads/devbox/version')),
|
|
87
91
|
};
|
|
88
92
|
}
|
|
89
93
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.52",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.52",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.52",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -359,6 +359,38 @@ function isBuildersHost(req) {
|
|
|
359
359
|
// Call this ONCE, right after app setup (trust proxy etc.) and BEFORE any
|
|
360
360
|
// entrypoint-specific tail (the game static + themed 404 in server.js; the plain
|
|
361
361
|
// 404 in platform-server.js).
|
|
362
|
+
// The Dev Box download/manifest mini-router (#904, ADR 0045), extracted so the
|
|
363
|
+
// ACTUAL route handlers are hermetically testable (tests/devbox_update_path.mjs
|
|
364
|
+
// mounts them on a throwaway app — the docs_openapi_route pattern) instead of
|
|
365
|
+
// only their pure builder. task 1002649: the manifest is served under BOTH
|
|
366
|
+
// /latest.json and an extensionless /latest twin — a front proxy with a
|
|
367
|
+
// file-matcher can eat *.json paths before Node (the live pre-ADR-0152 vhost
|
|
368
|
+
// 404'd latest.json empty while extensionless siblings worked), so every
|
|
369
|
+
// manifest consumer has a path that survives that class. Registration order
|
|
370
|
+
// matters: the literal routes must precede the `:target` catch-all.
|
|
371
|
+
function mountDevboxDownloads(app, devboxDownloads) {
|
|
372
|
+
const manifest = (_req, res) => {
|
|
373
|
+
// Short TTL: a release bump (config change + deploy) is visible within a
|
|
374
|
+
// minute, same posture as the hall's HTML.
|
|
375
|
+
res.set('Cache-Control', 'public, max-age=60');
|
|
376
|
+
res.json(devboxDownloads.latestJson());
|
|
377
|
+
};
|
|
378
|
+
app.get('/downloads/devbox/latest.json', manifest);
|
|
379
|
+
app.get('/downloads/devbox/latest', manifest);
|
|
380
|
+
// #924: the lightweight update-check endpoint the Dev Box app polls + the hall
|
|
381
|
+
// /settings courtesy line reads.
|
|
382
|
+
app.get('/downloads/devbox/version', (_req, res) => {
|
|
383
|
+
res.set('Cache-Control', 'public, max-age=60');
|
|
384
|
+
res.json(devboxDownloads.versionJson());
|
|
385
|
+
});
|
|
386
|
+
app.get('/downloads/devbox/:target', (req, res) => {
|
|
387
|
+
const url = devboxDownloads.redirectFor(req.params.target);
|
|
388
|
+
if (!url) return res.fail('unknown_target', 404, { targets: Object.keys(devboxDownloads.latestJson().assets) });
|
|
389
|
+
res.set('Cache-Control', 'public, max-age=60');
|
|
390
|
+
res.redirect(302, url);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
362
394
|
function mountInternalSurfaces(app) {
|
|
363
395
|
// Prometheus instrumentation (task #1680, V4.R28a). The counting middleware
|
|
364
396
|
// mounts FIRST so it wraps every surface registered below (and, since
|
|
@@ -477,26 +509,7 @@ function mountInternalSurfaces(app) {
|
|
|
477
509
|
// /builders/downloads/* and 404).
|
|
478
510
|
const devboxCfg = tryLoadInstanceConfig('devbox-app.json');
|
|
479
511
|
if (devboxCfg) {
|
|
480
|
-
|
|
481
|
-
app.get('/downloads/devbox/latest.json', (_req, res) => {
|
|
482
|
-
// Short TTL: a release bump (config change + deploy) is visible within a
|
|
483
|
-
// minute, same posture as the hall's HTML.
|
|
484
|
-
res.set('Cache-Control', 'public, max-age=60');
|
|
485
|
-
res.json(devboxDownloads.latestJson());
|
|
486
|
-
});
|
|
487
|
-
// #924: the lightweight update-check endpoint the Dev Box app polls + the hall
|
|
488
|
-
// /settings courtesy line reads. MUST be registered before the `:target` route
|
|
489
|
-
// below, or Express would treat "version" as a download target and 404 it.
|
|
490
|
-
app.get('/downloads/devbox/version', (_req, res) => {
|
|
491
|
-
res.set('Cache-Control', 'public, max-age=60');
|
|
492
|
-
res.json(devboxDownloads.versionJson());
|
|
493
|
-
});
|
|
494
|
-
app.get('/downloads/devbox/:target', (req, res) => {
|
|
495
|
-
const url = devboxDownloads.redirectFor(req.params.target);
|
|
496
|
-
if (!url) return res.fail('unknown_target', 404, { targets: Object.keys(devboxDownloads.latestJson().assets) });
|
|
497
|
-
res.set('Cache-Control', 'public, max-age=60');
|
|
498
|
-
res.redirect(302, url);
|
|
499
|
-
});
|
|
512
|
+
mountDevboxDownloads(app, buildDevboxDownloads(devboxCfg));
|
|
500
513
|
}
|
|
501
514
|
|
|
502
515
|
// Standalone `bongos` CLI downloads + the one-line installer (task 1248,
|
|
@@ -979,4 +992,4 @@ function mountInternalSurfaces(app) {
|
|
|
979
992
|
return app;
|
|
980
993
|
}
|
|
981
994
|
|
|
982
|
-
module.exports = { mountInternalSurfaces, isStatusHost, isBuildersHost, injectBrandingGlobal, injectBrandingInto, injectModuleHallWidgets, themeMarkup, applyBrandTokens, resolveOpenapiJson, openapiJson, tryLoadInstanceConfig };
|
|
995
|
+
module.exports = { mountInternalSurfaces, mountDevboxDownloads, isStatusHost, isBuildersHost, injectBrandingGlobal, injectBrandingInto, injectModuleHallWidgets, themeMarkup, applyBrandTokens, resolveOpenapiJson, openapiJson, tryLoadInstanceConfig };
|
package/src/module-api.js
CHANGED
|
@@ -529,8 +529,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
529
529
|
// landed since 1.19.49 with no explicit bump. run 31524280584. (task 1002620)
|
|
530
530
|
// 1.19.51 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
531
531
|
// landed since 1.19.50 with no explicit bump. run 31525032273. (task 1002620)
|
|
532
|
+
// 1.19.52 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
533
|
+
// landed since 1.19.51 with no explicit bump. run 31526131183. (task 1002620)
|
|
532
534
|
// ---------------------------------------------------------------------------
|
|
533
|
-
const CORE_VERSION = '1.19.
|
|
535
|
+
const CORE_VERSION = '1.19.52'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
534
536
|
|
|
535
537
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
536
538
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -119,5 +119,22 @@ t('config/devbox-app.json parses, resolves every target, and aliases point at re
|
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
console.log('update manifest (task 1002649 — the payload the devbox-app updater reads):');
|
|
123
|
+
|
|
124
|
+
t('committed config yields a NON-EMPTY manifest — version + at least one asset in both shapes', () => {
|
|
125
|
+
// The live symptom behind task 1002649 was latest.json arriving as an empty
|
|
126
|
+
// body. The route is a pure function of this committed config, so pin here
|
|
127
|
+
// that the manifest can never be empty at the source.
|
|
128
|
+
const d = buildDevboxDownloads(committedConfig);
|
|
129
|
+
for (const shape of [d.latestJson(), d.versionJson()]) {
|
|
130
|
+
assert.match(shape.version, /^\d+\.\d+\.\d+$/, 'manifest carries a semver version');
|
|
131
|
+
assert.ok(Object.keys(shape.assets).length >= 1, 'manifest carries at least one asset');
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// The client-path pin (extensionless /version, never .json) lives in
|
|
136
|
+
// tests/devbox_update_path.mjs — NOT here — so it still runs on the neutral
|
|
137
|
+
// core checkout where this file topology-skips (task 1002649).
|
|
138
|
+
|
|
122
139
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
123
140
|
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// tests/devbox_update_path.mjs — the devbox-app update-check path pin (task 1002649).
|
|
2
|
+
//
|
|
3
|
+
// Deliberately SEPARATE from tests/devbox_downloads.mjs: that file topology-
|
|
4
|
+
// skips wherever config/devbox-app.json is absent (this neutral core checkout),
|
|
5
|
+
// but this pin reads only the app client's SOURCE, so it must run everywhere —
|
|
6
|
+
// including this repo's `unit` lane.
|
|
7
|
+
//
|
|
8
|
+
// Why the pin exists: a front proxy with a file-matcher can eat *.json paths
|
|
9
|
+
// before they reach Node — the live pre-ADR-0152 vhost 404'd
|
|
10
|
+
// /downloads/devbox/latest.json with an empty body while the extensionless
|
|
11
|
+
// /downloads/devbox/version twin worked, silently breaking the app's update
|
|
12
|
+
// check. The updater only needs { version, assets }, which /version carries.
|
|
13
|
+
//
|
|
14
|
+
// Run: node --test tests/devbox_update_path.mjs
|
|
15
|
+
|
|
16
|
+
import { strict as assert } from 'node:assert';
|
|
17
|
+
import { test } from 'node:test';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
import { createRequire } from 'node:module';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const src = readFileSync(new URL('../modules/dev-box/app/src/gds-client.js', import.meta.url), 'utf8');
|
|
23
|
+
|
|
24
|
+
test('latestRelease polls the extensionless /downloads/devbox/version, never a .json path', () => {
|
|
25
|
+
const line = src.split('\n').find((l) => l.includes('latestRelease:'));
|
|
26
|
+
assert.ok(line, 'gds-client exposes latestRelease');
|
|
27
|
+
assert.ok(line.includes("'/downloads/devbox/version'"), 'latestRelease polls /downloads/devbox/version');
|
|
28
|
+
assert.ok(!line.includes('latest.json'), 'latestRelease must not poll a .json-suffixed path');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ---- The ACTUAL route handlers, over loopback (the docs_openapi_route ----
|
|
32
|
+
// pattern). This is the layer the repo owns: the Express routes must serve a
|
|
33
|
+
// NON-EMPTY manifest on all three paths and redirect targets. (The infra layer
|
|
34
|
+
// above Node — the host's front proxy — cannot be pinned from a unit test;
|
|
35
|
+
// that is blocker 1000105. These pins guarantee that when a request reaches
|
|
36
|
+
// Node, the manifest is never empty.)
|
|
37
|
+
const CFG = {
|
|
38
|
+
releases_repo: 'owner/devbox-releases',
|
|
39
|
+
version: '9.9.9',
|
|
40
|
+
assets: { 'win-x64': 'Setup-9.9.9-x64.exe', 'mac-arm64': 'App-9.9.9-arm64.dmg' },
|
|
41
|
+
aliases: { windows: 'win-x64', mac: 'mac-arm64' },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
async function withDevboxServer(fn) {
|
|
45
|
+
const express = require('express');
|
|
46
|
+
const { mountDevboxDownloads } = require('../src/bongos/serve-internal.js');
|
|
47
|
+
const { buildDevboxDownloads } = require('../src/devbox-downloads.js');
|
|
48
|
+
const app = express();
|
|
49
|
+
mountDevboxDownloads(app, buildDevboxDownloads(CFG));
|
|
50
|
+
const server = await new Promise((resolve) => { const s = app.listen(0, () => resolve(s)); });
|
|
51
|
+
try {
|
|
52
|
+
await fn(`http://127.0.0.1:${server.address().port}`);
|
|
53
|
+
} finally {
|
|
54
|
+
await new Promise((r) => server.close(r));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test('latest.json and the extensionless /latest twin both serve the full non-empty manifest', async () => {
|
|
59
|
+
await withDevboxServer(async (base) => {
|
|
60
|
+
for (const p of ['/downloads/devbox/latest.json', '/downloads/devbox/latest']) {
|
|
61
|
+
const r = await fetch(base + p);
|
|
62
|
+
assert.equal(r.status, 200, `${p} must 200`);
|
|
63
|
+
assert.match(r.headers.get('content-type') || '', /application\/json/, `${p} content-type`);
|
|
64
|
+
const j = await r.json();
|
|
65
|
+
assert.equal(j.version, '9.9.9');
|
|
66
|
+
assert.ok(Object.keys(j.assets).length >= 1, `${p} manifest must carry assets — the task-1002649 empty-body class`);
|
|
67
|
+
assert.equal(j.assets['win-x64'].file, 'Setup-9.9.9-x64.exe');
|
|
68
|
+
assert.match(j.assets['win-x64'].url, /\/releases\/download\/v9\.9\.9\//);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('/version serves the flat update-check payload; a target redirects 302', async () => {
|
|
74
|
+
await withDevboxServer(async (base) => {
|
|
75
|
+
const v = await fetch(base + '/downloads/devbox/version');
|
|
76
|
+
assert.equal(v.status, 200);
|
|
77
|
+
const j = await v.json();
|
|
78
|
+
assert.equal(j.version, '9.9.9');
|
|
79
|
+
assert.equal(typeof j.assets['win-x64'], 'string', 'flat {target: url} shape');
|
|
80
|
+
const r = await fetch(base + '/downloads/devbox/windows', { redirect: 'manual' });
|
|
81
|
+
assert.equal(r.status, 302);
|
|
82
|
+
assert.match(r.headers.get('location') || '', /Setup-9\.9\.9-x64\.exe$/);
|
|
83
|
+
});
|
|
84
|
+
});
|