@bongos/core 1.17.0 → 1.17.1
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 +154 -83
- package/CLAUDE.md +11 -238
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +4 -4
- package/clients/bongos-client/bongos-client.global.js +14 -1
- package/clients/bongos-client/index.cjs +14 -1
- package/clients/bongos-client/index.d.ts +19 -2
- package/clients/bongos-client/index.mjs +11 -1
- package/clients/bongos-client/package.json +1 -1
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
- package/docs/adr/0134-private-first-npm-distribution.md +20 -6
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
- package/docs/adr/README.md +5 -4
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +1 -1
- package/docs/file-map.md +2 -2
- package/docs/modules-contract.md +2 -0
- package/docs/recipes/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +22 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/upgrading-the-core.md +8 -8
- package/migrations/core_191_module_submissions.sql +45 -0
- package/modules/autonomy/CLAUDE.md +11 -45
- package/modules/builder-settings/CLAUDE.md +11 -54
- package/modules/dev-box/CLAUDE.md +11 -34
- package/modules/discord/CLAUDE.md +11 -46
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/new-project-wizard.js +5 -4
- package/modules/ideas/CLAUDE.md +11 -33
- package/modules/lifecycle/CLAUDE.md +11 -59
- package/modules/lifecycle/routes/goals.js +80 -0
- package/modules/memory/CLAUDE.md +11 -36
- package/modules/onboarding/CLAUDE.md +11 -51
- package/modules/security/CLAUDE.md +11 -40
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claude-materialize.js +151 -18
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +53 -13
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +13 -24
- package/scripts/gds/package-core.js +56 -14
- package/scripts/gds/provision.js +102 -17
- package/scripts/gds/seed-adopt-tasks.js +1 -1
- package/scripts/gds/status.js +6 -1
- package/scripts/gds/task.js +166 -0
- package/scripts/gds/upgrade.js +58 -18
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +11 -0
- package/src/bongos/module-submissions.js +56 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/routes/CLAUDE.md +11 -39
- package/src/bongos/routes/auth.js +6 -1
- package/src/bongos/routes/modules.js +123 -21
- package/src/instance-config.js +1 -1
- package/src/module-api.js +13 -1
- package/tests/claude_materialize.mjs +105 -0
- package/tests/device_flow_start.mjs +73 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/init.mjs +59 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +49 -0
- package/tests/package_core.mjs +68 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +29 -2
- package/tests/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/upgrade.mjs +89 -25
package/scripts/gds/init.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
// it refuses where config/branding.json exists, and --force OVERWRITES. `--adopt` is
|
|
38
38
|
// the opposite: it LAYERS Cloud Bongos onto an EXISTING repo (app code, git history,
|
|
39
39
|
// a package.json). It writes the lean config only where absent, adds the pinned
|
|
40
|
-
// @
|
|
40
|
+
// @bongos/core dependency to the owner's package.json without touching any other
|
|
41
41
|
// field, and materializes .claude/ ADDITIVELY — for any pre-existing file it
|
|
42
42
|
// MERGES-or-LEAVES-AND-REPORTS, it NEVER overwrites, and it does not reuse the --force
|
|
43
43
|
// path. It deliberately does NOT seed a synthetic "V1 — Prove the loop": capturing the
|
|
@@ -56,11 +56,17 @@
|
|
|
56
56
|
// bongos init --dir <path> target dir for the config files (default: cwd)
|
|
57
57
|
// bongos init --no-seed write config only; skip the version/task seeding
|
|
58
58
|
// bongos init --force overwrite existing config files (greenfield only; ignored by --adopt)
|
|
59
|
-
// bongos init --vendor-core <tgz> greenfield only: vendor the @
|
|
59
|
+
// bongos init --vendor-core <tgz> greenfield only: vendor the @bongos/core .tgz + pin
|
|
60
60
|
// package.json to file:vendor/… + generate package-lock.json,
|
|
61
61
|
// so the scaffold is `npm ci`-installable (ADR 0108 gap 1/4).
|
|
62
|
-
// Without it the scaffold pins the caret range (
|
|
63
|
-
//
|
|
62
|
+
// Without it the scaffold pins the caret range (which now
|
|
63
|
+
// resolves from the PRIVATE registry — see --registry-core).
|
|
64
|
+
// bongos init --registry-core greenfield only: keep the caret pin + write an env-fed
|
|
65
|
+
// .npmrc (`_authToken=${NPM_TOKEN}`) so `npm ci` pulls the
|
|
66
|
+
// PRIVATE @bongos/core from the npm registry (ADR 0134).
|
|
67
|
+
// With NPM_TOKEN set it also generates package-lock.json;
|
|
68
|
+
// without a token it 404s (the expected tokenless outcome).
|
|
69
|
+
// Mutually exclusive with --vendor-core (registry vs tarball).
|
|
64
70
|
|
|
65
71
|
const fs = require('node:fs');
|
|
66
72
|
const path = require('node:path');
|
|
@@ -72,6 +78,7 @@ const { materializeClaude } = require('./claude-materialize'); // ADR 0108 §3 (
|
|
|
72
78
|
const { detectStack, formatStackSummary } = require('./adopt-detect'); // ADR 0121 §Decision 4 (task 2043): inspect an existing repo before layering
|
|
73
79
|
const { resolveBacklogTasks, DEFAULT_MAX: BACKLOG_MAX } = require('./adopt-import'); // ADR 0121 §Decision 3 (task 2044): import an existing backlog
|
|
74
80
|
const { preflightAdopt, formatPreflight } = require('./adopt-preflight'); // ADR 0121 §Decision 5 (task 2045): the accept-gated conflict pre-flight
|
|
81
|
+
const { writeNpmrc, hasNpmToken } = require('./npmrc'); // ADR 0134 (task 2090): the registry-install path's env-fed .npmrc
|
|
75
82
|
|
|
76
83
|
const KNOWN_MODULES = ['game', 'art-pipeline', 'discord', 'dev-box'];
|
|
77
84
|
|
|
@@ -87,7 +94,7 @@ const HOSTING_SHAPES = ['co-tenant', 'dedicated', 'standalone'];
|
|
|
87
94
|
// `bongos init --adopt` LAYERS this onto an existing repo's package.json; `bongos
|
|
88
95
|
// upgrade` later re-pins it to a vendored `file:` tarball. Named so the adopt
|
|
89
96
|
// layering and the upgrade re-pin agree on the dependency name.
|
|
90
|
-
const CORE_PKG = '@
|
|
97
|
+
const CORE_PKG = '@bongos/core';
|
|
91
98
|
|
|
92
99
|
const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
|
|
93
100
|
|
|
@@ -114,13 +121,13 @@ function coreVersionSafe() {
|
|
|
114
121
|
// (`bash <core>/scripts/migrate.sh`). Keeping them identical means `npm start` /
|
|
115
122
|
// `npm run migrate` on the instance behave like production, and `bongos upgrade`'s
|
|
116
123
|
// `npm run migrate` step resolves to the core's migrate.sh (ADR 0108).
|
|
117
|
-
const INSTANCE_CORE_PKG_DIR = 'node_modules/@
|
|
124
|
+
const INSTANCE_CORE_PKG_DIR = 'node_modules/@bongos/core';
|
|
118
125
|
const INSTANCE_START_SCRIPT = `node ${INSTANCE_CORE_PKG_DIR}/src/platform-server.js`;
|
|
119
126
|
const INSTANCE_MIGRATE_SCRIPT = `bash ${INSTANCE_CORE_PKG_DIR}/scripts/migrate.sh`;
|
|
120
127
|
|
|
121
128
|
// PURE (exported for tests) — build/augment the greenfield instance package.json so a
|
|
122
129
|
// fresh instance is `npm ci`-installable and runnable (ADR 0108 greenfield gap 1/4,
|
|
123
|
-
// task 2053): the @
|
|
130
|
+
// task 2053): the @bongos/core dependency (the `file:` tarball pin when a tarball is
|
|
124
131
|
// vendored, else the declared caret range — the CARET RESOLVES NOWHERE on npm, which is
|
|
125
132
|
// exactly why the vendored path pins `file:`) plus the start/migrate npm scripts a
|
|
126
133
|
// standalone instance runs. Preserves any pre-existing fields + scripts (fills only what
|
|
@@ -258,7 +265,7 @@ function buildHierarchyConfig(spec) {
|
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
// PURE — layer the pinned core dependency into a parsed package.json (ADR 0121
|
|
261
|
-
// §Decision 2 / task 2042). NEVER clobbers: if `@
|
|
268
|
+
// §Decision 2 / task 2042). NEVER clobbers: if `@bongos/core` is already a dep
|
|
262
269
|
// (in dependencies OR devDependencies) it returns the object untouched with
|
|
263
270
|
// action:'present'; if there is no package.json at all it MINTS a minimal one
|
|
264
271
|
// (action:'created'); otherwise it ADDS the dep, preserving every existing field and
|
|
@@ -466,7 +473,7 @@ async function interview({ adopt = false } = {}) {
|
|
|
466
473
|
let provisioning;
|
|
467
474
|
const wantProv = (await ask(rl, ' Provision production hosting now? (y/N)', 'N')).toLowerCase();
|
|
468
475
|
if (wantProv === 'y' || wantProv === 'yes') {
|
|
469
|
-
const shape = (await ask(rl, ' Hosting shape? (co-tenant = share an existing box, $0 / dedicated = its own droplet / standalone = its own repo + pinned @
|
|
476
|
+
const shape = (await ask(rl, ' Hosting shape? (co-tenant = share an existing box, $0 / dedicated = its own droplet / standalone = its own repo + pinned @bongos/core, ADR 0108)', 'co-tenant')).toLowerCase();
|
|
470
477
|
const domain = await ask(rl, ' Domain you own — bring-your-own (blank = none yet)?');
|
|
471
478
|
const targetRef = await ask(rl, ' Target credential ref? (which control-plane creds the runner should use; optional)');
|
|
472
479
|
let tier = '';
|
|
@@ -579,7 +586,7 @@ function layerConfigs(spec, { dir, dryRun, log = console.log }) {
|
|
|
579
586
|
}
|
|
580
587
|
|
|
581
588
|
// `--adopt` package.json layering (ADR 0121 §Decision 2 / task 2042): add the
|
|
582
|
-
// pinned @
|
|
589
|
+
// pinned @bongos/core dependency to the owner's EXISTING package.json without
|
|
583
590
|
// touching any other field, or MINT a minimal one when the repo has none. When the
|
|
584
591
|
// dep is already present the file is left byte-untouched (no reserialize). An
|
|
585
592
|
// unparseable package.json is left alone with a report — adopt never risks
|
|
@@ -785,10 +792,15 @@ async function main(argv) {
|
|
|
785
792
|
const adopt = hasFlag('--adopt', args); // brownfield: LAYER onto an existing repo (ADR 0121)
|
|
786
793
|
const accept = hasFlag('--accept', args) || hasFlag('--yes', args) || hasFlag('-y', args); // adopt pre-flight accept-gate (§Decision 5)
|
|
787
794
|
const vendorCore = arg('--vendor-core', args); // greenfield: vendor + file:-pin a core .tgz, then generate the lockfile (ADR 0108 gap 1/4, task 2053)
|
|
795
|
+
const registryCore = hasFlag('--registry-core', args); // greenfield: caret pin + env-fed .npmrc → `npm ci` pulls the PRIVATE core from the registry (ADR 0134, task 2090)
|
|
788
796
|
|
|
789
797
|
if (adopt && force) {
|
|
790
798
|
console.error('init: --force is a greenfield-only overwrite; --adopt never overwrites, so --force is ignored.');
|
|
791
799
|
}
|
|
800
|
+
if (vendorCore && registryCore) {
|
|
801
|
+
console.error('init: --vendor-core and --registry-core are mutually exclusive — the vendored tarball and the registry pull are two ways to resolve the same core dependency. Pick one.');
|
|
802
|
+
return 1;
|
|
803
|
+
}
|
|
792
804
|
|
|
793
805
|
let raw;
|
|
794
806
|
if (fromPath) {
|
|
@@ -841,7 +853,7 @@ async function main(argv) {
|
|
|
841
853
|
console.log(`\nScaffolding "${spec.identity.productName}"${dryRun ? ' (dry run — nothing will be written or created)' : ''}:`);
|
|
842
854
|
writeConfigs(spec, { dir, force, dryRun, coreDep });
|
|
843
855
|
|
|
844
|
-
// Vendor the pinned @
|
|
856
|
+
// Vendor the pinned @bongos/core tarball + generate the lockfile so the scaffold is
|
|
845
857
|
// `npm ci --omit=dev`-installable by the provisioner (ADR 0108 gap 1/4, task 2053). The
|
|
846
858
|
// `npm install` here is the ONLY heavy step — it CREATES package-lock.json from the file:
|
|
847
859
|
// tarball; the provisioner later runs `npm ci` against that committed lockfile.
|
|
@@ -849,14 +861,42 @@ async function main(argv) {
|
|
|
849
861
|
if (dryRun) {
|
|
850
862
|
console.log(`\n[dry-run] would vendor ${vendorPlan.tgz} → ${vendorPlan.vendorRel} and run \`npm install\` to generate package-lock.json`);
|
|
851
863
|
} else {
|
|
852
|
-
console.log('\nVendoring the pinned @
|
|
864
|
+
console.log('\nVendoring the pinned @bongos/core + generating the lockfile:');
|
|
853
865
|
upgrade.vendorTarball({ instanceDir: dir, fromTgz: vendorPlan.tgz, targetVersion: vendorPlan.version });
|
|
854
866
|
console.log(` ✓ vendored ${vendorPlan.vendorRel}`);
|
|
855
867
|
const inst = upgrade.npmInstall(dir);
|
|
856
868
|
if (!inst.ok) {
|
|
857
869
|
console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — package-lock.json not generated; run \`npm install\` in ${dir} by hand before provisioning.`);
|
|
858
870
|
} else {
|
|
859
|
-
console.log(' ✓ npm install → package-lock.json generated (node_modules/@
|
|
871
|
+
console.log(' ✓ npm install → package-lock.json generated (node_modules/@bongos/core installed)');
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Registry-install path (ADR 0134, task 2090): keep the caret pin writeConfigs wrote
|
|
877
|
+
// and add an env-fed .npmrc so `npm ci` pulls the PRIVATE @bongos/core from the
|
|
878
|
+
// registry. With NPM_TOKEN present we also generate package-lock.json (a reproducible
|
|
879
|
+
// `npm ci` later); without a token we still write the caret pin + .npmrc so the
|
|
880
|
+
// scaffold is registry-shaped, and say plainly that the private package 404s until a
|
|
881
|
+
// token is provided — the expected tokenless outcome the ADR calls to verify.
|
|
882
|
+
if (registryCore) {
|
|
883
|
+
if (dryRun) {
|
|
884
|
+
console.log(`\n[dry-run] would write .npmrc (env-fed @bongos auth) and ${hasNpmToken()
|
|
885
|
+
? 'run `npm install` to generate package-lock.json from the registry'
|
|
886
|
+
: 'SKIP lockfile generation (no NPM_TOKEN — the private core 404s until one is provided)'}`);
|
|
887
|
+
} else {
|
|
888
|
+
console.log('\nWiring the registry-install path (ADR 0134):');
|
|
889
|
+
writeNpmrc(dir, { log: console.log });
|
|
890
|
+
if (hasNpmToken()) {
|
|
891
|
+
const inst = upgrade.npmInstall(dir);
|
|
892
|
+
if (!inst.ok) {
|
|
893
|
+
console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — confirm NPM_TOKEN reaches the private @bongos org; package-lock.json not generated.`);
|
|
894
|
+
} else {
|
|
895
|
+
console.log(' ✓ npm install → package-lock.json generated (pulled @bongos/core from the registry)');
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
console.log(' • no NPM_TOKEN in the environment — wrote the caret pin + .npmrc but skipped lockfile generation.');
|
|
899
|
+
console.log(' Provide a read-only automation token (paid `bongos` org) as NPM_TOKEN before `npm ci`; without it the private package 404s cleanly.');
|
|
860
900
|
}
|
|
861
901
|
}
|
|
862
902
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// DISCIPLINE. Every migration SOURCE owns a distinct filename namespace, so stems can
|
|
15
15
|
// never collide across sources and each source's allocator counts only its own numbers:
|
|
16
|
-
// • CORE (the @
|
|
16
|
+
// • CORE (the @bongos/core package's migrations/*.sql) → core_NNN_slug.sql
|
|
17
17
|
// • MODULE (modules/<key>/migrations/*.sql) → <key>_NNN_slug.sql (key: dev-box → dev_box_)
|
|
18
18
|
// • INSTANCE (the host repo's migrations/instance/*.sql) → NNN_slug.sql (BARE — the instance owns the bare namespace)
|
|
19
19
|
//
|
package/scripts/gds/module.js
CHANGED
|
@@ -351,6 +351,40 @@ function recordSignOff(key, signedBy, { modulesDir = MODULES_DIR } = {}) {
|
|
|
351
351
|
return { ...record, path: path.relative(REPO_ROOT, file) };
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
// Reads a previously-recorded sign-off (recordSignOff above), or null if the
|
|
355
|
+
// module has never been signed off. Never throws on a missing/garbled file —
|
|
356
|
+
// "no sign-off yet" is the expected common case, not an error.
|
|
357
|
+
function readSignOff(key, { modulesDir = MODULES_DIR } = {}) {
|
|
358
|
+
return readJson(path.join(modulesDir, key, '.upstream-signoff.json'));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// bongos module submit <key> — ADR 0107 §1 upstreaming submission
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
// Packages a module for the upstreaming queue: its manifest + a full file
|
|
366
|
+
// snapshot (base64, so binary assets round-trip identically) + whatever
|
|
367
|
+
// sign-off is on disk. Pure fs read — no pre-check, no network; the caller
|
|
368
|
+
// (cmdSubmit locally, the POST /modules/:key/submit route server-side) is
|
|
369
|
+
// responsible for having already run checkModulePublishability() and for
|
|
370
|
+
// deciding whether a missing sign-off is fatal.
|
|
371
|
+
function packageModuleForSubmission(key, { modulesDir = MODULES_DIR } = {}) {
|
|
372
|
+
const dir = path.join(modulesDir, key);
|
|
373
|
+
const manifest = readJson(path.join(dir, 'module.json'));
|
|
374
|
+
if (!manifest) throw new Error(`modules/${key}/module.json missing or unreadable`);
|
|
375
|
+
let totalBytes = 0;
|
|
376
|
+
const files = listModuleFiles(dir)
|
|
377
|
+
.map((abs) => {
|
|
378
|
+
const rel = path.relative(dir, abs).split(path.sep).join('/');
|
|
379
|
+
const buf = fs.readFileSync(abs);
|
|
380
|
+
totalBytes += buf.length;
|
|
381
|
+
return { path: rel, contentBase64: buf.toString('base64'), bytes: buf.length };
|
|
382
|
+
})
|
|
383
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
384
|
+
const signOff = readSignOff(key, { modulesDir });
|
|
385
|
+
return { key, manifest, files, totalBytes, signOff };
|
|
386
|
+
}
|
|
387
|
+
|
|
354
388
|
// ---------------------------------------------------------------------------
|
|
355
389
|
// Commands
|
|
356
390
|
// ---------------------------------------------------------------------------
|
|
@@ -445,6 +479,79 @@ function cmdCheck(args, { log = console.log, errlog = console.error, check = che
|
|
|
445
479
|
return 0;
|
|
446
480
|
}
|
|
447
481
|
|
|
482
|
+
// `bongos module submit <key>` — ADR 0107 §1. Runs the §4 pre-check + requires
|
|
483
|
+
// a sign-off (locally, for fast refusal without a network round trip), then
|
|
484
|
+
// POSTs to POST /modules/:key/submit — the SAME route the hall "Modules" tab's
|
|
485
|
+
// submit button calls (src/bongos/routes/modules.js). That route re-runs the
|
|
486
|
+
// pre-check against ITS OWN on-disk copy of the module and is the authoritative
|
|
487
|
+
// gate: a local pass here is a courtesy, not a guarantee, if the module's
|
|
488
|
+
// changes aren't committed + shipped + deployed yet.
|
|
489
|
+
async function cmdSubmit(args, {
|
|
490
|
+
log = console.log, errlog = console.error,
|
|
491
|
+
check = checkModulePublishability, sign = recordSignOff, readSign = readSignOff,
|
|
492
|
+
submit,
|
|
493
|
+
} = {}) {
|
|
494
|
+
const key = args.find((a) => !a.startsWith('-'));
|
|
495
|
+
if (!key) {
|
|
496
|
+
errlog('usage: bongos module submit <key> [--sign-off "Name <email>"]');
|
|
497
|
+
return 2;
|
|
498
|
+
}
|
|
499
|
+
const signIdx = args.indexOf('--sign-off');
|
|
500
|
+
const signedBy = signIdx !== -1 ? args[signIdx + 1] : null;
|
|
501
|
+
|
|
502
|
+
log(`bongos module submit — upstreaming "${key}" (ADR 0107 §1)`);
|
|
503
|
+
|
|
504
|
+
let result;
|
|
505
|
+
try { result = check(key); }
|
|
506
|
+
catch (e) { errlog(`bongos module submit: ${e.message}`); return 1; }
|
|
507
|
+
|
|
508
|
+
if (!result.ok) {
|
|
509
|
+
for (const f of result.findings) errlog(` ✗ ${f.kind}: ${f.detail}`);
|
|
510
|
+
errlog(`\nREFUSING: "${key}" is not clear to submit upstream. Fix the finding(s) above and re-run.`);
|
|
511
|
+
return 1;
|
|
512
|
+
}
|
|
513
|
+
log(' ✓ AGPL/publish pre-check passed.');
|
|
514
|
+
|
|
515
|
+
let signOff = readSign(key);
|
|
516
|
+
if (!signOff && signedBy) {
|
|
517
|
+
const record = sign(key, signedBy);
|
|
518
|
+
log(` ✓ Sign-off recorded — ${record.path}`);
|
|
519
|
+
signOff = readSign(key);
|
|
520
|
+
}
|
|
521
|
+
if (!signOff) {
|
|
522
|
+
errlog(`\nREFUSING: no DCO-style sign-off on file for "${key}". Pass --sign-off "Name <email>" (or run \`bongos module check ${key} --sign-off "Name <email>"\` first).`);
|
|
523
|
+
return 1;
|
|
524
|
+
}
|
|
525
|
+
log(` ✓ Sign-off on file — "${signOff.signOff.signedBy}", ${signOff.signOff.date}.`);
|
|
526
|
+
|
|
527
|
+
const doSubmit = submit || (async (k, body) => {
|
|
528
|
+
// Lazy require: cli-lib.js carries session/fetch plumbing that must NEVER
|
|
529
|
+
// load into the always-on server process — this file is also required by
|
|
530
|
+
// src/bongos/routes/modules.js for the pre-check it reuses. A real network
|
|
531
|
+
// call only happens when a human/agent actually invokes this CLI command.
|
|
532
|
+
const { cliClient, requireSession } = require('./cli-lib');
|
|
533
|
+
await requireSession();
|
|
534
|
+
const api = await cliClient();
|
|
535
|
+
return api.modules.postModulesKeySubmit({ key: k, body });
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
let res;
|
|
539
|
+
try { res = await doSubmit(key, { signed_by: signedBy || undefined }); }
|
|
540
|
+
catch (e) { errlog(`bongos module submit: request failed — ${e.message}`); return 1; }
|
|
541
|
+
|
|
542
|
+
if (!res.ok) {
|
|
543
|
+
const detail = (res.data && (res.data.message || res.data.error)) || JSON.stringify(res.data);
|
|
544
|
+
errlog(`\nREFUSING: the review queue rejected the submission (HTTP ${res.status}): ${detail}`);
|
|
545
|
+
if (res.status === 422) errlog('(the server re-runs the pre-check/sign-off check against its OWN deployed copy — commit + ship first if this module has local-only changes.)');
|
|
546
|
+
return 1;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const submission = res.data && res.data.submission;
|
|
550
|
+
log(`\n✓ Filed — submission id ${submission ? submission.id : '?'} for "${key}", status ${submission ? submission.status : 'pending'}.`);
|
|
551
|
+
log('Task 1771 (core-side review & accept flow) picks it up from here.');
|
|
552
|
+
return 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
448
555
|
function printHelp(out = console.log) {
|
|
449
556
|
out('bongos module — scaffold + manage Cloud Bongos modules\n');
|
|
450
557
|
out('Usage:');
|
|
@@ -452,14 +559,17 @@ function printHelp(out = console.log) {
|
|
|
452
559
|
out(' bongos upgrade Pre-check enabled modules against the core version (fail-closed)');
|
|
453
560
|
out(' bongos module check <key> [--sign-off "Name <email>"]');
|
|
454
561
|
out(' AGPL/publish pre-check before upstreaming + record a DCO-style sign-off');
|
|
562
|
+
out(' bongos module submit <key> [--sign-off "Name <email>"]');
|
|
563
|
+
out(' Pre-check + sign-off, then file into the rank-gated upstreaming review queue');
|
|
455
564
|
}
|
|
456
565
|
|
|
457
|
-
function main(argv) {
|
|
566
|
+
async function main(argv) {
|
|
458
567
|
const args = argv.slice(2);
|
|
459
568
|
const sub = args[0];
|
|
460
569
|
if (sub === 'new') return cmdNew(args.slice(1));
|
|
461
570
|
if (sub === 'upgrade') return cmdUpgrade(args.slice(1));
|
|
462
571
|
if (sub === 'check') return cmdCheck(args.slice(1));
|
|
572
|
+
if (sub === 'submit') return cmdSubmit(args.slice(1));
|
|
463
573
|
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
464
574
|
printHelp();
|
|
465
575
|
return sub ? 0 : 2;
|
|
@@ -470,13 +580,17 @@ function main(argv) {
|
|
|
470
580
|
}
|
|
471
581
|
|
|
472
582
|
module.exports = {
|
|
473
|
-
main, cmdNew, cmdUpgrade, cmdCheck, printHelp,
|
|
583
|
+
main, cmdNew, cmdUpgrade, cmdCheck, cmdSubmit, printHelp,
|
|
474
584
|
scaffoldModule, gatherUpgrade, evaluateUpgrade, buildManifest,
|
|
475
|
-
checkModulePublishability, recordSignOff, DCO_STATEMENT,
|
|
585
|
+
checkModulePublishability, recordSignOff, readSignOff, DCO_STATEMENT,
|
|
586
|
+
listModuleFiles, packageModuleForSubmission,
|
|
476
587
|
routeTemplate, claudeMdTemplate, titleize, camel, coreVersion,
|
|
477
588
|
MODULES_DIR,
|
|
478
589
|
};
|
|
479
590
|
|
|
480
591
|
if (require.main === module) {
|
|
481
|
-
|
|
592
|
+
Promise.resolve(main(process.argv)).then(
|
|
593
|
+
(code) => process.exit(code),
|
|
594
|
+
(e) => { console.error(e); process.exit(1); }
|
|
595
|
+
);
|
|
482
596
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
//
|
|
4
|
+
// scripts/gds/npmrc.js — env-fed `.npmrc` for installing the PRIVATE @bongos/* core
|
|
5
|
+
// from the npm registry (ADR 0134, task 2090). The single place that knows the
|
|
6
|
+
// registry host + scope + the `.npmrc` shape + the opt-in/token gates, shared by:
|
|
7
|
+
// • init.js — the greenfield `--registry-core` scaffold
|
|
8
|
+
// • provision.js — the standalone scaffold's registry path (opt-in, fail-soft)
|
|
9
|
+
// • upgrade.js — the `--registry <version>` update channel
|
|
10
|
+
//
|
|
11
|
+
// WHY private + env-fed. `@bongos/core` publishes as a SCOPED, PRIVATE package (ADR
|
|
12
|
+
// 0134): a private scoped package can only be installed with auth. Consumers
|
|
13
|
+
// authenticate with an `.npmrc` whose token EXPANDS FROM THE ENVIRONMENT at install
|
|
14
|
+
// time (`_authToken=${NPM_TOKEN}`) — so the committed file carries NO secret, and a
|
|
15
|
+
// tokenless install 404s cleanly (the expected pre-launch outcome).
|
|
16
|
+
//
|
|
17
|
+
// The vendored-tarball path (task 2053) stays the DEFAULT/fallback until this registry
|
|
18
|
+
// path is proven end-to-end on a real greenfield standup (ADR 0134 §Consequences).
|
|
19
|
+
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
|
|
23
|
+
const REGISTRY_HOST = 'registry.npmjs.org';
|
|
24
|
+
const CORE_SCOPE = '@bongos'; // the paid, canonical npm org (ADR 0134 scope correction, task 2092)
|
|
25
|
+
const TOKEN_ENV = 'NPM_TOKEN';
|
|
26
|
+
const REQUEST_ENV = 'PROVISION_CORE_FROM_REGISTRY';
|
|
27
|
+
|
|
28
|
+
// The `.npmrc` a scaffolded instance commits. The token is the LITERAL `${NPM_TOKEN}`
|
|
29
|
+
// (npm expands env vars in `.npmrc` values at read time), so the file is safe to
|
|
30
|
+
// commit — it holds no secret. Scoping @bongos to the registry keeps every unrelated
|
|
31
|
+
// install on the default public registry.
|
|
32
|
+
function npmrcContent({ scope = CORE_SCOPE, host = REGISTRY_HOST, tokenEnv = TOKEN_ENV } = {}) {
|
|
33
|
+
return [
|
|
34
|
+
`; Cloud Bongos — env-fed auth for the PRIVATE ${scope}/* packages (ADR 0134, task 2090).`,
|
|
35
|
+
`; The token EXPANDS FROM THE ENVIRONMENT at install time; nothing secret is committed here.`,
|
|
36
|
+
`; \`npm ci\` needs ${tokenEnv} set (a read-only automation token from the paid \`bongos\` org);`,
|
|
37
|
+
`; without it the private package 404s — that is the expected tokenless outcome.`,
|
|
38
|
+
`${scope}:registry=https://${host}/`,
|
|
39
|
+
`//${host}/:_authToken=\${${tokenEnv}}`,
|
|
40
|
+
'',
|
|
41
|
+
].join('\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Is a registry token present in the environment? The fail-soft gate: provisioning
|
|
45
|
+
// falls back to the vendored tarball when this is false, so a tokenless standup still
|
|
46
|
+
// works (never a broken instance).
|
|
47
|
+
function hasNpmToken(env = process.env) {
|
|
48
|
+
return !!String(env[TOKEN_ENV] || '').trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Has the operator opted the standalone scaffold into the registry path? OFF by
|
|
52
|
+
// default — the vendored tarball stays the default until the registry path is proven
|
|
53
|
+
// end-to-end (ADR 0134). '1'/'true'/'yes'/'on' (case-insensitive) → true.
|
|
54
|
+
function registryCoreRequested(env = process.env) {
|
|
55
|
+
return /^(1|true|yes|on)$/i.test(String(env[REQUEST_ENV] || '').trim());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Write the env-fed `.npmrc` into an instance dir. Default overwrites (a greenfield
|
|
59
|
+
// scaffold owns the file); pass overwrite:false to leave a pre-existing `.npmrc`
|
|
60
|
+
// untouched (an upgrade must not clobber a consumer's other registries). Returns the
|
|
61
|
+
// path (or null when skipped). Dry-run writes nothing.
|
|
62
|
+
function writeNpmrc(dir, { fsImpl = fs, dryRun = false, overwrite = true, log = () => {} } = {}) {
|
|
63
|
+
const p = path.join(dir, '.npmrc');
|
|
64
|
+
if (!overwrite && fsImpl.existsSync(p)) {
|
|
65
|
+
log(` • .npmrc already present — left untouched (add \`${CORE_SCOPE}:registry\` + \`_authToken=\${${TOKEN_ENV}}\` if the private core doesn't resolve).`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (dryRun) {
|
|
69
|
+
log(` [dry-run] would write ${p} (env-fed ${CORE_SCOPE} auth; token via $${TOKEN_ENV}, never committed)`);
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
fsImpl.writeFileSync(p, npmrcContent());
|
|
73
|
+
log(` ✓ wrote .npmrc (env-fed ${CORE_SCOPE} auth; token via $${TOKEN_ENV}, never committed)`);
|
|
74
|
+
return p;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
REGISTRY_HOST, CORE_SCOPE, TOKEN_ENV, REQUEST_ENV,
|
|
79
|
+
npmrcContent, hasNpmToken, registryCoreRequested, writeNpmrc,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Tiny CLI: `node scripts/gds/npmrc.js [--print | --write <dir>]` — print the `.npmrc`
|
|
83
|
+
// body or materialize it into <dir>. Handy for a manual registry-standup rehearsal.
|
|
84
|
+
if (require.main === module) {
|
|
85
|
+
const args = process.argv.slice(2);
|
|
86
|
+
const writeIdx = args.indexOf('--write');
|
|
87
|
+
if (writeIdx !== -1 && args[writeIdx + 1]) {
|
|
88
|
+
const p = writeNpmrc(path.resolve(args[writeIdx + 1]), { log: console.log });
|
|
89
|
+
process.stdout.write(`${p}\n`);
|
|
90
|
+
} else {
|
|
91
|
+
process.stdout.write(npmrcContent());
|
|
92
|
+
}
|
|
93
|
+
}
|
package/scripts/gds/onboard.js
CHANGED
|
@@ -22,28 +22,12 @@ const oauth = require(path.join(__dirname, 'oauth-secret.js')); // validateC
|
|
|
22
22
|
|
|
23
23
|
// ── PURE core (exported for tests) ───────────────────────────────────────────
|
|
24
24
|
|
|
25
|
-
// The canonical step sequence
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
function onboardPlan(mode = 'greenfield') {
|
|
32
|
-
const adopt = mode === 'adopt';
|
|
33
|
-
return [
|
|
34
|
-
{ key: 'name-address', kind: 'auto', label: 'Decide name + address (interview)' },
|
|
35
|
-
{ key: 'github-repo', kind: 'human', label: adopt ? 'Use your EXISTING GitHub repository' : 'Create the empty GitHub repository' },
|
|
36
|
-
{ key: 'dns-preflight', kind: 'auto', label: 'DNS token preflight — before the address step (task 1980)' },
|
|
37
|
-
{ key: 'init', kind: 'auto', label: adopt ? 'bongos init --adopt — LAYER onto the existing repo (pre-flight + accept)' : 'bongos init — scaffold the instance config' },
|
|
38
|
-
{ key: 'scaffold', kind: 'auto', label: adopt ? 'Layer config + pinned @cloudbongos/core onto the existing repo (no clobber)' : 'Scaffold the standalone repo (own repo + pinned @cloudbongos/core)' },
|
|
39
|
-
{ key: 'provision', kind: 'auto', label: 'Provision core + DB + address + service (standalone, task 1978)' },
|
|
40
|
-
{ key: 'oauth-app', kind: 'human', label: 'Wire GitHub sign-in — one-click GitHub App (task 2080), or a manual OAuth app' },
|
|
41
|
-
{ key: 'oauth-place', kind: 'auto', label: 'Place the OAuth secret — auto via the Manifest flow, or verified manual (task 1979)' },
|
|
42
|
-
{ key: 'first-sign-in', kind: 'human', label: 'First sign-in (seats the founding Archon)' },
|
|
43
|
-
{ key: 'verify', kind: 'auto', label: 'Verify /healthz auth_configured' },
|
|
44
|
-
{ key: 'brand', kind: 'auto', label: 'Brand the instance look' },
|
|
45
|
-
];
|
|
46
|
-
}
|
|
25
|
+
// The canonical step sequence now lives in ONE place — the provisioning module — so the
|
|
26
|
+
// CLI (here), the branded /create page, and the hall wizard can't drift (task 2085; goal
|
|
27
|
+
// 35 / criterion 165: "the canonical step sequence is the source of truth; skill/CLI/hall
|
|
28
|
+
// are forms over it"). The web forms read it over GET /provisioning/onboard-plan; the CLI
|
|
29
|
+
// requires it directly. Re-exported below so `bongos onboard` + tests keep the same API.
|
|
30
|
+
const { onboardPlan } = require(path.join(__dirname, '..', '..', 'modules', 'provisioning', 'onboard-plan.js'));
|
|
47
31
|
|
|
48
32
|
// The exact GitHub OAuth-app values for a domain. The callback is PINNED to the apex
|
|
49
33
|
// /api/gds/auth/web/callback (auth.js WEB_REDIRECT_ORIGIN — a sign-in begun on a
|
|
@@ -54,6 +38,10 @@ function oauthAppInstructions(domain) {
|
|
|
54
38
|
location: "GitHub → your ACCOUNT's Settings → Developer settings → OAuth Apps → New OAuth App",
|
|
55
39
|
homepageUrl: origin,
|
|
56
40
|
callbackUrl: `${origin}/api/gds/auth/web/callback`,
|
|
41
|
+
// GitHub disables Device Flow on a new OAuth App by DEFAULT, but the terminal
|
|
42
|
+
// `bongos login` uses it — leaving it off makes CLI sign-in fail with
|
|
43
|
+
// device_flow_disabled (the 2026-07-08 demo login-500). Must be enabled. (task 2127)
|
|
44
|
+
enableDeviceFlow: 'Check "Enable Device Flow" on the app page and save — off by default; the terminal `bongos login` needs it.',
|
|
57
45
|
};
|
|
58
46
|
}
|
|
59
47
|
|
|
@@ -149,13 +137,13 @@ async function run() {
|
|
|
149
137
|
// Step 5 — the standalone repo (greenfield scaffolds it; adopt already layered it in ③)
|
|
150
138
|
if (adopt) {
|
|
151
139
|
log('\n④ Your repo is now the standalone instance (ADR 0108):');
|
|
152
|
-
log(' `bongos init --adopt` already layered config/ + .claude/ + the pinned @
|
|
140
|
+
log(' `bongos init --adopt` already layered config/ + .claude/ + the pinned @bongos/core dep onto it.');
|
|
153
141
|
log(` Commit + push ${a.repoOwner}/${a.repoName}. That checkout is what the service runs.`);
|
|
154
142
|
await ask(rl, 'Press Enter once the layered repo is committed + pushed…');
|
|
155
143
|
} else {
|
|
156
144
|
log('\n④ Scaffold the STANDALONE repo (ADR 0108):');
|
|
157
145
|
log(` Clone ${a.repoOwner}/${a.repoName} to the control-plane standalone root, add the config/ + .claude/`);
|
|
158
|
-
log(' from init, pin @
|
|
146
|
+
log(' from init, pin @bongos/core in its package.json, commit + push. That checkout is what the service runs.');
|
|
159
147
|
await ask(rl, 'Press Enter once the standalone repo is pushed…');
|
|
160
148
|
}
|
|
161
149
|
|
|
@@ -180,6 +168,7 @@ async function run() {
|
|
|
180
168
|
log(' (Developer settings is under your ACCOUNT, not the repository.)');
|
|
181
169
|
log(` Homepage URL: ${oa.homepageUrl}`);
|
|
182
170
|
log(` Authorization callback URL: ${oa.callbackUrl}`);
|
|
171
|
+
log(` Enable Device Flow: ✅ ${oa.enableDeviceFlow}`);
|
|
183
172
|
log(' Copy the Client ID; Generate a new client secret and copy it (shown once).');
|
|
184
173
|
const clientId = await ask(rl, 'Paste the OAuth Client ID:');
|
|
185
174
|
const clientSecret = await ask(rl, 'Paste the OAuth Client Secret:');
|