@bongos/core 1.19.99 → 1.19.101
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 +39 -24
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/adr/0167-module-catalog-source-vs-provenance.md +44 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +2 -0
- package/docs/file-map.md +2 -0
- package/docs/modules-contract.md +2 -0
- package/modules/hall-ui/public/modules.css +30 -1
- package/modules/hall-ui/public/modules.html +24 -13
- package/modules/hall-ui/public/modules.js +86 -25
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +0 -5
- package/public-landing/projects.html +0 -11
- package/scripts/gds/module.js +135 -1
- package/src/bongos/routes/modules.js +23 -24
- package/src/module-api.js +5 -1
- package/src/module-loader/catalog.js +217 -0
- package/tests/module_catalog.mjs +304 -0
- package/tests/projects_hub.mjs +40 -0
|
@@ -540,14 +540,10 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
540
540
|
box-shadow:0 0 0 3px color-mix(in srgb,var(--acc) 22%,transparent);}
|
|
541
541
|
.tbl .ptitle{font-family:var(--font-display);font-weight:700;font-size:15px;color:var(--ink);}
|
|
542
542
|
.tbl .ptag{font-size:12.5px;color:var(--ink-faint);font-weight:600;}
|
|
543
|
-
.tbl .num{font-family:var(--font-mono);font-weight:500;}
|
|
544
543
|
.tbl .pill{display:inline-block;font-family:var(--font-mono);font-size:11px;padding:3px 10px;
|
|
545
544
|
border-radius:999px;background:var(--accent-soft);color:var(--accent-deep);}
|
|
546
545
|
:root[data-mode="dark"] .tbl .pill{color:var(--sun-lit);}
|
|
547
546
|
@media (prefers-color-scheme: dark){:root:not([data-mode="light"]) .tbl .pill{color:var(--sun-lit);}}
|
|
548
|
-
.tbl .tags{display:flex;gap:6px;flex-wrap:wrap;}
|
|
549
|
-
.tbl .tags span{font-family:var(--font-mono);font-size:10.5px;padding:2px 8px;border-radius:999px;
|
|
550
|
-
border:1px solid var(--line);color:var(--ink-faint);}
|
|
551
547
|
.tbl td.right{text-align:right;}
|
|
552
548
|
/* Nothing in an action or status cell may wrap: "Manage →" breaking after the
|
|
553
549
|
space and the chip's recency dropping to a second line both read as a broken
|
|
@@ -557,7 +553,6 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
557
553
|
.tbl .colStatus{width:1%;white-space:nowrap;}
|
|
558
554
|
.tbl .colGo{width:1%;white-space:nowrap;padding-left:8px;}
|
|
559
555
|
.tbl .colName{width:auto;}
|
|
560
|
-
@media (max-width:780px){.tbl .colTags,.tbl .colType{display:none;}}
|
|
561
556
|
|
|
562
557
|
/* ── the project card ──
|
|
563
558
|
An overlay INSIDE the map, not a destination: opening a project must not cost
|
|
@@ -182,9 +182,6 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
|
|
|
182
182
|
<thead>
|
|
183
183
|
<tr>
|
|
184
184
|
<th class="colName" data-sort="name" aria-sort="ascending">Project</th>
|
|
185
|
-
<th class="right" data-sort="builders">Builders</th>
|
|
186
|
-
<th class="colType">Type</th>
|
|
187
|
-
<th class="colTags">Tags</th>
|
|
188
185
|
<th class="right colStatus">Status</th>
|
|
189
186
|
<th class="colGo"></th>
|
|
190
187
|
</tr>
|
|
@@ -199,9 +196,6 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
|
|
|
199
196
|
<td class="colName"><span class="pname"><span class="pdot" style="--acc:var(--accent)"></span>
|
|
200
197
|
<span><span class="ptitle" data-brand-product-inline>Cloud Bongos</span><span id="selfPill"></span><br>
|
|
201
198
|
<span class="ptag">this platform</span></span></span></td>
|
|
202
|
-
<td class="right num">—</td>
|
|
203
|
-
<td class="colType">—</td>
|
|
204
|
-
<td class="colTags">—</td>
|
|
205
199
|
<td class="right colStatus"><span class="chip calm"><span class="cdot"></span>—</span></td>
|
|
206
200
|
<td class="colGo"><a class="btn small ghost" href="#" data-self-open>Open →</a></td>
|
|
207
201
|
</tr>
|
|
@@ -628,11 +622,6 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
|
|
|
628
622
|
accentOf(p) + '"></span>' +
|
|
629
623
|
'<span><span class="ptitle">' + esc(p.name) + '</span>' + pill + '<br>' +
|
|
630
624
|
'<span class="ptag">' + esc(p.tagline || '') + '</span></span></span></td>' +
|
|
631
|
-
/* the public projection carries no builder count, type or tags — an
|
|
632
|
-
em dash is the truthful cell, an invented number is not */
|
|
633
|
-
'<td class="right num">—</td>' +
|
|
634
|
-
'<td class="colType">—</td>' +
|
|
635
|
-
'<td class="colTags">—</td>' +
|
|
636
625
|
/* liveness on a catalog row is NOT probed by this surface. A chip that
|
|
637
626
|
said "live" here would claim more than anything verified (C6); the
|
|
638
627
|
verification overlay on owned orbs is its own task. */
|
package/scripts/gds/module.js
CHANGED
|
@@ -22,6 +22,15 @@
|
|
|
22
22
|
// version of the loader's silent coreVersion SKIP (which would just drop the
|
|
23
23
|
// module at boot). Disabled mismatches are warnings, not blockers.
|
|
24
24
|
//
|
|
25
|
+
// bongos module list [--catalog] [--search <q>] [--json]
|
|
26
|
+
// ADR 0107 §2/§3 (task 1001772) — the BROWSABLE CATALOG from the terminal:
|
|
27
|
+
// every module this instance can enable, each with its author + origin
|
|
28
|
+
// credit, its enabled state, and who maintains it. `--catalog` narrows to
|
|
29
|
+
// the CORE-shipped modules (the shared catalog an instance opts into, §6);
|
|
30
|
+
// without it you also see the instance's own modules. It renders the same
|
|
31
|
+
// model as the hall's Modules tab (src/module-loader/catalog.js), so the two
|
|
32
|
+
// surfaces cannot disagree about who built what.
|
|
33
|
+
//
|
|
25
34
|
// bongos module check <key>
|
|
26
35
|
// ADR 0107 §4 — the submit-time pre-check a module must clear before it can
|
|
27
36
|
// be upstreamed into the shared core catalog. REUSES (never re-types) the
|
|
@@ -48,6 +57,7 @@
|
|
|
48
57
|
// Usage:
|
|
49
58
|
// bongos module new <key> scaffold modules/<key>/
|
|
50
59
|
// bongos module new <key> --force overwrite an existing modules/<key>/
|
|
60
|
+
// bongos module list [--catalog] browse the module catalog (author + origin credit)
|
|
51
61
|
// bongos upgrade pre-check enabled modules vs the core version
|
|
52
62
|
// bongos module check <key> [--sign-off "Name <email>"] AGPL/publish pre-check + DCO sign-off
|
|
53
63
|
|
|
@@ -60,6 +70,7 @@ const MODULES_DIR = path.join(REPO_ROOT, 'modules');
|
|
|
60
70
|
const { validateManifest, assertValidManifest, KEY_RE, SUNSET_STATUSES } = require('../../src/module-loader/manifest-schema');
|
|
61
71
|
const { parseVersion, cmp } = require('../../src/module-loader/semver');
|
|
62
72
|
const loader = require('../../src/module-loader/loader');
|
|
73
|
+
const catalog = require('../../src/module-loader/catalog');
|
|
63
74
|
const { resolveModules, MODULE_REGISTRY, NEUTRAL_PATH, INSTANCE_PATH } = require('../../src/modules');
|
|
64
75
|
// The public-mirror publish boundary (ADR 0098) — REUSED, never re-typed, for
|
|
65
76
|
// the ADR 0107 §4 upstreaming pre-check. Only matchesDeny is used (not the full
|
|
@@ -327,6 +338,125 @@ function gatherUpgrade({ modulesDir = MODULES_DIR, core = coreVersion(), env = p
|
|
|
327
338
|
return { rows, errors, core };
|
|
328
339
|
}
|
|
329
340
|
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// bongos module list — ADR 0107 §2/§3 the browsable catalog (task 1001772)
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
// The live catalog off disk + config. Unlike gatherUpgrade (which scans ONE
|
|
346
|
+
// modulesDir because it is answering a question about this checkout's own
|
|
347
|
+
// modules), this uses the loader's DEFAULT roots so a split instance sees both
|
|
348
|
+
// trees — the core package's modules/ AND its own (ADR 0108 §1). That two-root
|
|
349
|
+
// scan is what makes "core-shipped vs. built here" a structural fact here
|
|
350
|
+
// rather than a guess.
|
|
351
|
+
function gatherCatalog({ roots, core = coreVersion(), env = process.env, neutral, instance, prefix } = {}) {
|
|
352
|
+
neutral = neutral || readJson(NEUTRAL_PATH) || {};
|
|
353
|
+
instance = instance || readJson(INSTANCE_PATH) || {};
|
|
354
|
+
prefix = prefix || envPrefix();
|
|
355
|
+
|
|
356
|
+
const { loaded, skipped, errors } = loader.discoverModules(roots ? { roots, core } : { core });
|
|
357
|
+
|
|
358
|
+
// Same registry-spanning rule as gatherUpgrade: resolveModules throws on an
|
|
359
|
+
// unknown key, so every discovered key must be in the registry it is given.
|
|
360
|
+
const registry = { ...MODULE_REGISTRY };
|
|
361
|
+
for (const { key, manifest } of loaded) {
|
|
362
|
+
if (!(key in registry)) registry[key] = { default: manifest.default === true, contributes: manifest.contributes || {} };
|
|
363
|
+
}
|
|
364
|
+
for (const { key } of skipped) {
|
|
365
|
+
if (key && !(key in registry)) registry[key] = { default: false, contributes: {} };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let enabled = {};
|
|
369
|
+
try { enabled = resolveModules({ neutral, instance, env, envPrefix: prefix, registry }); }
|
|
370
|
+
catch { /* a malformed config must not blank the catalog — show it all as disabled */ }
|
|
371
|
+
|
|
372
|
+
const { coreRoot, instanceRoot, coreCheckout } = catalog.detectRoots();
|
|
373
|
+
const rows = catalog.buildCatalog({
|
|
374
|
+
loaded, enabled, coreRoot, instanceRoot, coreCheckout, ownProjectName: ownProjectName(),
|
|
375
|
+
});
|
|
376
|
+
return { rows, skipped, errors, core };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// The instance's own product name, for the credit axis (deriveProvenance).
|
|
380
|
+
// Fail-soft: no brand pack resolvable → null, which makes provenance fall back
|
|
381
|
+
// to "own-built" rather than guessing (provenance.js's documented behaviour).
|
|
382
|
+
function ownProjectName() {
|
|
383
|
+
try { return require('../../src/branding').branding().identity.productName; } catch { return null; }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function pad(s, n) { return String(s == null ? '' : s).padEnd(n); }
|
|
387
|
+
function clip(s, n) {
|
|
388
|
+
const v = String(s == null ? '' : s);
|
|
389
|
+
return v.length > n ? `${v.slice(0, n - 1)}…` : v;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Pure renderer — every input arrives as a parameter (the tests pass catalog
|
|
393
|
+
// rows straight in), so it touches no disk and no config.
|
|
394
|
+
function formatCatalog(rows, { catalogOnly = false, query = '', core = null } = {}) {
|
|
395
|
+
const scoped = catalogOnly ? catalog.catalogRows(rows) : rows;
|
|
396
|
+
const shown = catalog.searchCatalog(scoped, query);
|
|
397
|
+
const counts = catalog.summarize(rows);
|
|
398
|
+
const out = [];
|
|
399
|
+
|
|
400
|
+
out.push(catalogOnly
|
|
401
|
+
? 'bongos module list --catalog — core modules this instance can enable (ADR 0107 §6: opt-in, default-off)'
|
|
402
|
+
: 'bongos module list — every module on disk, with author + origin credit (ADR 0107 §2)');
|
|
403
|
+
if (core) out.push(` core (src/module-api.js CORE_VERSION): ${core}`);
|
|
404
|
+
out.push(` ${counts.catalog} in the core catalog (${counts.catalog_enabled} enabled) · ${counts.instance} built here · ${counts.total} total`);
|
|
405
|
+
if (query) out.push(` filter "${query}" — ${shown.length} match(es)`);
|
|
406
|
+
out.push('');
|
|
407
|
+
|
|
408
|
+
if (!shown.length) {
|
|
409
|
+
out.push(query ? ' no module matches that filter.' : ' no modules discovered.');
|
|
410
|
+
return out.join('\n');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
out.push(` ${pad('', 5)}${pad('KEY', 20)}${pad('SOURCE', 10)}${pad('AUTHOR', 20)}${pad('ORIGIN', 26)}${pad('LICENSE', 12)}UPKEEP`);
|
|
414
|
+
for (const r of shown) {
|
|
415
|
+
const mark = r.enabled ? '[x]' : '[ ]';
|
|
416
|
+
// An inferred credit is marked so it never reads as something the author
|
|
417
|
+
// typed — the catalog's whole job is honest attribution (ADR 0107 §2).
|
|
418
|
+
const suffix = r.credit.inferred ? '*' : '';
|
|
419
|
+
const upkeep = (r.maintenance && r.maintenance.status) || 'undeclared';
|
|
420
|
+
out.push(` ${pad(mark, 5)}${pad(clip(r.key, 19), 20)}${pad(r.source, 10)}${pad(clip((r.credit.author || '—') + suffix, 19), 20)}${pad(clip((r.credit.origin || '—') + suffix, 25), 26)}${pad(clip(r.license || '—', 11), 12)}${upkeep}`);
|
|
421
|
+
}
|
|
422
|
+
out.push('');
|
|
423
|
+
out.push(' [x] enabled here · [ ] available, not enabled');
|
|
424
|
+
if (shown.some((r) => r.credit.inferred)) {
|
|
425
|
+
out.push(` * credit not declared in module.json — shown as the platform (${catalog.CORE_PROJECT_NAME}), which ships it.`);
|
|
426
|
+
}
|
|
427
|
+
out.push(' enable one: add it to config/modules.json, or use the hall\'s Modules tab (Metic+).');
|
|
428
|
+
return out.join('\n');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function cmdList(args, { log = console.log, errlog = console.error, gather = gatherCatalog } = {}) {
|
|
432
|
+
const catalogOnly = args.includes('--catalog');
|
|
433
|
+
const json = args.includes('--json');
|
|
434
|
+
const sIdx = args.indexOf('--search');
|
|
435
|
+
const query = sIdx !== -1 ? (args[sIdx + 1] || '') : '';
|
|
436
|
+
if (sIdx !== -1 && (!args[sIdx + 1] || args[sIdx + 1].startsWith('--'))) {
|
|
437
|
+
errlog('usage: bongos module list [--catalog] [--search <text>] [--json]');
|
|
438
|
+
return 2;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let data;
|
|
442
|
+
try { data = gather(); }
|
|
443
|
+
catch (e) { errlog(`bongos module list: ${e.message}`); return 1; }
|
|
444
|
+
|
|
445
|
+
if (json) {
|
|
446
|
+
const scoped = catalogOnly ? catalog.catalogRows(data.rows) : data.rows;
|
|
447
|
+
const modules = catalog.searchCatalog(scoped, query);
|
|
448
|
+
log(JSON.stringify({ core: data.core, summary: catalog.summarize(data.rows), modules }, null, 2));
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
log(formatCatalog(data.rows, { catalogOnly, query, core: data.core }));
|
|
453
|
+
for (const s of data.skipped) {
|
|
454
|
+
errlog(` ! ${s.key}: not listed — ${s.reason}`);
|
|
455
|
+
}
|
|
456
|
+
for (const e of data.errors) errlog(` ! ${e.dir}: ${(e.errors || []).join('; ')}`);
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
330
460
|
// ---------------------------------------------------------------------------
|
|
331
461
|
// bongos module check <key> — ADR 0107 §4 upstreaming pre-check + DCO sign-off
|
|
332
462
|
// ---------------------------------------------------------------------------
|
|
@@ -649,6 +779,8 @@ function printHelp(out = console.log) {
|
|
|
649
779
|
out('bongos module — scaffold + manage Cloud Bongos modules\n');
|
|
650
780
|
out('Usage:');
|
|
651
781
|
out(' bongos module new <key> Scaffold modules/<key>/ (manifest + skeleton + sample route + CLAUDE.md)');
|
|
782
|
+
out(' bongos module list [--catalog] [--search <text>] [--json]');
|
|
783
|
+
out(' Browse the module catalog — what you can enable, with author + origin credit');
|
|
652
784
|
out(' bongos upgrade Pre-check enabled modules against the core version (fail-closed)');
|
|
653
785
|
out(' bongos module check <key> [--sign-off "Name <email>"]');
|
|
654
786
|
out(' AGPL/publish pre-check before upstreaming + record a DCO-style sign-off');
|
|
@@ -660,6 +792,7 @@ async function main(argv) {
|
|
|
660
792
|
const args = argv.slice(2);
|
|
661
793
|
const sub = args[0];
|
|
662
794
|
if (sub === 'new') return cmdNew(args.slice(1));
|
|
795
|
+
if (sub === 'list' || sub === 'ls') return cmdList(args.slice(1));
|
|
663
796
|
if (sub === 'upgrade') return cmdUpgrade(args.slice(1));
|
|
664
797
|
if (sub === 'check') return cmdCheck(args.slice(1));
|
|
665
798
|
if (sub === 'submit') return cmdSubmit(args.slice(1));
|
|
@@ -673,7 +806,8 @@ async function main(argv) {
|
|
|
673
806
|
}
|
|
674
807
|
|
|
675
808
|
module.exports = {
|
|
676
|
-
main, cmdNew, cmdUpgrade, cmdCheck, cmdSubmit, printHelp,
|
|
809
|
+
main, cmdNew, cmdList, cmdUpgrade, cmdCheck, cmdSubmit, printHelp,
|
|
810
|
+
gatherCatalog, formatCatalog, ownProjectName,
|
|
677
811
|
scaffoldModule, gatherUpgrade, evaluateUpgrade, buildManifest,
|
|
678
812
|
formatMaintenanceHint, isPastRemoveAfter,
|
|
679
813
|
checkModulePublishability, recordSignOff, readSignOff, DCO_STATEMENT,
|
|
@@ -43,7 +43,7 @@ const path = require('node:path');
|
|
|
43
43
|
const auth = require('../auth');
|
|
44
44
|
const { asyncHandler, validateOrRespond, LIMITS } = require('./_helpers');
|
|
45
45
|
const loader = require('../../module-loader/loader');
|
|
46
|
-
const
|
|
46
|
+
const catalog = require('../../module-loader/catalog');
|
|
47
47
|
const modulesLib = require('../../modules');
|
|
48
48
|
// scripts/gds/ is a sibling CORE-package dir (both resolve under
|
|
49
49
|
// resolveCoreRoot() — see scripts/gds/CLAUDE.md), so requiring its pure fs
|
|
@@ -80,31 +80,26 @@ function readInstanceConfig() {
|
|
|
80
80
|
catch (e) { if (e.code === 'ENOENT') return { modules: {} }; throw e; }
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// The catalog model (src/module-loader/catalog.js) is the SINGLE builder behind
|
|
84
|
+
// both surfaces — this route and `bongos module list` — so the hall and the
|
|
85
|
+
// terminal can never disagree about who built a module or which tree it ships
|
|
86
|
+
// from (task 1001772). Every field the pre-catalog version returned is still
|
|
87
|
+
// here, byte-for-byte; what's new is `source` (core-shipped vs. built here,
|
|
88
|
+
// read structurally from the discovery root) and `credit` (the display
|
|
89
|
+
// attribution, never blank for a core module, with `inferred` marking a value
|
|
90
|
+
// the manifest didn't declare). `author`/`origin` stay the DECLARED manifest
|
|
91
|
+
// values — `provenance` is derived from those, not from the display credit.
|
|
83
92
|
function listModules() {
|
|
84
93
|
const { loaded } = loader.discoverModules();
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
enabled: enabledSet[key] === true,
|
|
95
|
-
coreVersion: manifest.coreVersion,
|
|
96
|
-
contributes: manifest.contributes || {},
|
|
97
|
-
provenance,
|
|
98
|
-
author: manifest.author || null,
|
|
99
|
-
origin: manifest.origin || null,
|
|
100
|
-
license: manifest.license || null,
|
|
101
|
-
maintainer: manifest.maintainer || null,
|
|
102
|
-
// Upkeep posture (ADR 0107 §7 / ADR 0166) — the catalog's job is to show
|
|
103
|
-
// who keeps a module true as the core advances, so a deprecated or
|
|
104
|
-
// unowned entry reads as such instead of looking like any other module.
|
|
105
|
-
maintenance: manifest.maintenance || null,
|
|
106
|
-
};
|
|
107
|
-
}).sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
94
|
+
const { coreRoot, instanceRoot, coreCheckout } = catalog.detectRoots();
|
|
95
|
+
return catalog.buildCatalog({
|
|
96
|
+
loaded,
|
|
97
|
+
enabled: modulesLib.modules(),
|
|
98
|
+
coreRoot,
|
|
99
|
+
instanceRoot,
|
|
100
|
+
coreCheckout,
|
|
101
|
+
ownProjectName: ownProjectName(),
|
|
102
|
+
});
|
|
108
103
|
}
|
|
109
104
|
|
|
110
105
|
module.exports = function buildModulesRouter() {
|
|
@@ -117,6 +112,10 @@ module.exports = function buildModulesRouter() {
|
|
|
117
112
|
modules: all,
|
|
118
113
|
own_built: all.filter((m) => m.provenance === 'own').length,
|
|
119
114
|
core_loaded: all.filter((m) => m.provenance === 'core').length,
|
|
115
|
+
// The catalog roll-up (task 1001772): how many modules the core ships that
|
|
116
|
+
// this instance can opt into, and how many it has. Distinct from the
|
|
117
|
+
// own_built/core_loaded pair above, which counts the CREDIT axis.
|
|
118
|
+
catalog: catalog.summarize(all),
|
|
120
119
|
submit_pipeline_live: true, // task 1770 shipped — POST /modules/:key/submit is real
|
|
121
120
|
});
|
|
122
121
|
}));
|
package/src/module-api.js
CHANGED
|
@@ -625,8 +625,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
625
625
|
// landed since 1.19.97 with no explicit bump. run 31749841394. (task 1002620)
|
|
626
626
|
// 1.19.99 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
627
627
|
// landed since 1.19.98 with no explicit bump. run 31750941866. (task 1002620)
|
|
628
|
+
// 1.19.100 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
629
|
+
// landed since 1.19.99 with no explicit bump. run 31751275973. (task 1002620)
|
|
630
|
+
// 1.19.101 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
631
|
+
// landed since 1.19.100 with no explicit bump. run 31752022322. (task 1002620)
|
|
628
632
|
// ---------------------------------------------------------------------------
|
|
629
|
-
const CORE_VERSION = '1.19.
|
|
633
|
+
const CORE_VERSION = '1.19.101'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
630
634
|
|
|
631
635
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
632
636
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/module-loader/catalog.js — the browsable module catalog (ADR 0107 §2/§3,
|
|
2
|
+
// task 1001772, the last task of goal 1000029). The "larger repository of
|
|
3
|
+
// modules built by the sub-projects": every module an instance can enable, each
|
|
4
|
+
// carrying its author + origin credit.
|
|
5
|
+
//
|
|
6
|
+
// Two surfaces read this ONE model so they cannot drift: the hall's Modules tab
|
|
7
|
+
// (GET /api/bongos/modules) and `bongos module list --catalog`. Pure functions
|
|
8
|
+
// only — the fs/config reads live in detectRoots() at the bottom, and every
|
|
9
|
+
// builder below takes its inputs injected so the tests drive it hermetically.
|
|
10
|
+
//
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// The two axes, and why they are NOT the same question
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
//
|
|
15
|
+
// source WHICH TREE does this module ship from — the core package, or
|
|
16
|
+
// this instance's own repo? Structural: it comes from the loader's
|
|
17
|
+
// discovery root (src/module-loader/loader.js scans both, ADR 0108
|
|
18
|
+
// §1). This is the CATALOG axis — "core" means an instance can
|
|
19
|
+
// enable it because the core shipped it.
|
|
20
|
+
//
|
|
21
|
+
// provenance WHO BUILT it — read off the manifest's `origin` string
|
|
22
|
+
// (provenance.js, ADR 0107 §9). This is the CREDIT axis.
|
|
23
|
+
//
|
|
24
|
+
// They were conflated before this file existed: the Modules tab derived
|
|
25
|
+
// "core-loaded" from `origin` alone, so in a split instance every core module
|
|
26
|
+
// (economy, governance, hall-ui…) — none of which declares an `origin`, because
|
|
27
|
+
// none was upstreamed from anywhere — read as "built here". A module's author
|
|
28
|
+
// and the tree it ships from are independent facts; the catalog needs both.
|
|
29
|
+
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const { deriveProvenance } = require('./provenance');
|
|
33
|
+
|
|
34
|
+
// The core's own published + source package names. A checkout whose package.json
|
|
35
|
+
// says one of these IS the core, so its modules/ IS the catalog (see
|
|
36
|
+
// coreCheckout below). Mirrors CORE_PKG in scripts/gds/{init,upgrade,update-channel}.js.
|
|
37
|
+
const CORE_PACKAGE_NAMES = ['@bongos/core', 'cloud-bongos'];
|
|
38
|
+
|
|
39
|
+
// The credit a core-shipped module carries when its manifest declares none.
|
|
40
|
+
// Core's own modules predate the ADR 0107 §2 attribution fields and mostly leave
|
|
41
|
+
// them unset; the honest answer is "the platform authored this", not a blank
|
|
42
|
+
// cell. Callers may override (an instance rebrands nothing here — this names the
|
|
43
|
+
// PLATFORM, not the host instance, and is the same string platform-identity's
|
|
44
|
+
// manifest already carries).
|
|
45
|
+
const CORE_PROJECT_NAME = 'Cloud Bongos';
|
|
46
|
+
|
|
47
|
+
// Is `child` inside `parent`? Path-segment aware, so ".../modules-extra" never
|
|
48
|
+
// counts as inside ".../modules".
|
|
49
|
+
function isInside(child, parent) {
|
|
50
|
+
if (!child || !parent) return false;
|
|
51
|
+
const rel = path.relative(path.resolve(parent), path.resolve(child));
|
|
52
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function samePath(a, b) {
|
|
56
|
+
if (!a || !b) return false;
|
|
57
|
+
return path.resolve(a) === path.resolve(b);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Does this checkout's package.json name it as the core itself?
|
|
61
|
+
function isCorePackageName(name) {
|
|
62
|
+
return CORE_PACKAGE_NAMES.includes(String(name || '').trim());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// WHICH TREE a discovered module ships from. `dir` is the module directory the
|
|
66
|
+
// loader recorded (…/modules/<key>).
|
|
67
|
+
//
|
|
68
|
+
// The roots are DISTINCT in a split instance (core in node_modules/@bongos/core,
|
|
69
|
+
// the instance at the repo root) — then this is a purely structural read and is
|
|
70
|
+
// exactly right. They COINCIDE in a single checkout, where the same directory is
|
|
71
|
+
// both roots and the structure can no longer answer it; there we fall back to
|
|
72
|
+
// what the checkout says it is: the core repo's modules/ is the catalog, any
|
|
73
|
+
// other checkout's is that instance's own.
|
|
74
|
+
function deriveSource(dir, { coreRoot, instanceRoot, coreCheckout = false } = {}) {
|
|
75
|
+
if (coreRoot && instanceRoot && !samePath(coreRoot, instanceRoot)) {
|
|
76
|
+
if (isInside(dir, path.join(coreRoot, 'modules'))) return 'core';
|
|
77
|
+
return 'instance';
|
|
78
|
+
}
|
|
79
|
+
return coreCheckout ? 'core' : 'instance';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Author + origin credit for one module (ADR 0107 §2 — a portable RECORD, never
|
|
83
|
+
// a cross-GDS foreign key). This is the DISPLAY credit; the declared manifest
|
|
84
|
+
// fields stay untouched beside it on every row, because "what the manifest says"
|
|
85
|
+
// and "what the catalog shows" are different questions and callers need both
|
|
86
|
+
// (tests/modules_route.mjs asserts provenance off the DECLARED origin).
|
|
87
|
+
//
|
|
88
|
+
// `inferred` is true when either value did not come from the manifest, so a
|
|
89
|
+
// surface can mark it — an inferred credit must never read as a claim the author
|
|
90
|
+
// typed, which is the whole point of an attribution record.
|
|
91
|
+
function creditFor(manifest = {}, { source = 'instance', coreProjectName = CORE_PROJECT_NAME } = {}) {
|
|
92
|
+
const declaredAuthor = typeof manifest.author === 'string' && manifest.author.trim() ? manifest.author.trim() : null;
|
|
93
|
+
const declaredOrigin = typeof manifest.origin === 'string' && manifest.origin.trim() ? manifest.origin.trim() : null;
|
|
94
|
+
const fallback = source === 'core' ? coreProjectName : null;
|
|
95
|
+
return {
|
|
96
|
+
author: declaredAuthor || fallback,
|
|
97
|
+
origin: declaredOrigin || fallback,
|
|
98
|
+
inferred: !(declaredAuthor && declaredOrigin) && Boolean(fallback),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// One catalog row per discovered module. `loaded` is the loader's
|
|
103
|
+
// discoverModules().loaded ([{ key, dir, manifest }]); `enabled` is the resolved
|
|
104
|
+
// enablement map (src/modules.js modules()).
|
|
105
|
+
function buildCatalog({
|
|
106
|
+
loaded = [],
|
|
107
|
+
enabled = {},
|
|
108
|
+
coreRoot,
|
|
109
|
+
instanceRoot,
|
|
110
|
+
coreCheckout = false,
|
|
111
|
+
coreProjectName = CORE_PROJECT_NAME,
|
|
112
|
+
ownProjectName = null,
|
|
113
|
+
} = {}) {
|
|
114
|
+
return loaded
|
|
115
|
+
.map(({ key, dir, manifest = {} }) => {
|
|
116
|
+
const source = deriveSource(dir, { coreRoot, instanceRoot, coreCheckout });
|
|
117
|
+
const credit = creditFor(manifest, { source, coreProjectName });
|
|
118
|
+
return {
|
|
119
|
+
key,
|
|
120
|
+
title: manifest.title || key,
|
|
121
|
+
description: manifest.description || '',
|
|
122
|
+
source,
|
|
123
|
+
// The credit axis, unchanged from ADR 0107 §9 — kept alongside `source`
|
|
124
|
+
// so the hall's existing own/core-loaded split keeps working verbatim.
|
|
125
|
+
provenance: deriveProvenance(manifest.origin, ownProjectName),
|
|
126
|
+
enabled: enabled[key] === true,
|
|
127
|
+
default: manifest.default === true,
|
|
128
|
+
coreVersion: manifest.coreVersion || null,
|
|
129
|
+
contributes: manifest.contributes || {},
|
|
130
|
+
// DECLARED — exactly what module.json says (null when it says nothing).
|
|
131
|
+
author: manifest.author || null,
|
|
132
|
+
origin: manifest.origin || null,
|
|
133
|
+
// DISPLAY — never blank for a core-shipped module; `inferred` marks it.
|
|
134
|
+
credit,
|
|
135
|
+
license: manifest.license || null,
|
|
136
|
+
maintainer: manifest.maintainer || null,
|
|
137
|
+
maintenance: manifest.maintenance || null,
|
|
138
|
+
};
|
|
139
|
+
})
|
|
140
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The CATALOG proper — the core-shipped modules an instance can opt into
|
|
144
|
+
// (ADR 0107 §6: accepted modules land default-off, every instance may enable).
|
|
145
|
+
function catalogRows(rows = []) {
|
|
146
|
+
return rows.filter((r) => r.source === 'core');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// The instance's own modules — the other half of the Modules tab, and the pool
|
|
150
|
+
// `bongos module submit` draws from.
|
|
151
|
+
function instanceRows(rows = []) {
|
|
152
|
+
return rows.filter((r) => r.source === 'instance');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Free-text browse over the fields a builder would actually search by. Empty
|
|
156
|
+
// query returns everything (so a surface can pipe through it unconditionally).
|
|
157
|
+
function searchCatalog(rows = [], query = '') {
|
|
158
|
+
const q = String(query || '').trim().toLowerCase();
|
|
159
|
+
if (!q) return rows;
|
|
160
|
+
return rows.filter((r) => [r.key, r.title, r.description, r.credit.author, r.credit.origin, r.license]
|
|
161
|
+
.some((f) => f && String(f).toLowerCase().includes(q)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function summarize(rows = []) {
|
|
165
|
+
const core = catalogRows(rows);
|
|
166
|
+
return {
|
|
167
|
+
total: rows.length,
|
|
168
|
+
catalog: core.length,
|
|
169
|
+
instance: rows.length - core.length,
|
|
170
|
+
enabled: rows.filter((r) => r.enabled).length,
|
|
171
|
+
catalog_enabled: core.filter((r) => r.enabled).length,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// The one impure helper — resolve the roots + "is this the core checkout?"
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
// Fail-soft by construction: a missing/garbled package.json means "not the core
|
|
180
|
+
// checkout", which degrades the catalog to "everything is this instance's own"
|
|
181
|
+
// rather than crashing a page load or a CLI listing.
|
|
182
|
+
function computeRoots({ coreRoot, instanceRoot } = {}) {
|
|
183
|
+
const cfg = require('../instance-config');
|
|
184
|
+
const core = coreRoot || cfg.resolveCoreRoot();
|
|
185
|
+
// NB resolveInstanceRoot() itself attempts a sync read of bongos.config.json —
|
|
186
|
+
// another reason this whole result is computed once, not per request.
|
|
187
|
+
const instance = instanceRoot || cfg.resolveInstanceRoot();
|
|
188
|
+
let name = null;
|
|
189
|
+
try { name = JSON.parse(fs.readFileSync(path.join(core, 'package.json'), 'utf8')).name; } catch { /* not the core, or unreadable */ }
|
|
190
|
+
return { coreRoot: core, instanceRoot: instance, coreCheckout: isCorePackageName(name), corePackageName: name || null };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Cached singleton — the same lazy-memo + `_reset()` pattern loader.js uses for
|
|
194
|
+
// `discovered()`, and for the same reason: this reads the filesystem, the answer
|
|
195
|
+
// is invariant for the process lifetime (a running server's core root and
|
|
196
|
+
// package identity cannot change under it), and `listModules()` is on the
|
|
197
|
+
// request path for GET /modules. Recomputing two blocking readFileSync calls per
|
|
198
|
+
// request to re-learn a constant is the kind of cost that only shows up under
|
|
199
|
+
// load, so it is paid once at first use.
|
|
200
|
+
//
|
|
201
|
+
// An EXPLICIT root bypasses the cache entirely: a caller passing its own roots
|
|
202
|
+
// is asking about a specific tree, not about this process, and must never get a
|
|
203
|
+
// memoized answer for a different one (nor poison the cache for everyone else).
|
|
204
|
+
let _rootsCache = null;
|
|
205
|
+
function detectRoots({ coreRoot, instanceRoot } = {}) {
|
|
206
|
+
if (coreRoot || instanceRoot) return computeRoots({ coreRoot, instanceRoot });
|
|
207
|
+
return (_rootsCache ||= computeRoots());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Drop the memo — for tests, and for any tool that relocates the roots mid-process.
|
|
211
|
+
function _resetRoots() { _rootsCache = null; }
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
buildCatalog, catalogRows, instanceRows, searchCatalog, summarize,
|
|
215
|
+
deriveSource, creditFor, isCorePackageName, detectRoots, _resetRoots,
|
|
216
|
+
CORE_PACKAGE_NAMES, CORE_PROJECT_NAME,
|
|
217
|
+
};
|