@bongos/core 1.20.83 → 1.21.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/.bongos-core.json +186 -81
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +7 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0051-full-session-transcript-corpus.md +3 -1
- package/docs/adr/0361-merges-publish-as-candidates-release-decides-what-others-are-offered.md +3 -3
- package/docs/adr/README.md +1 -1
- package/docs/api/openapi.json +153 -4
- package/docs/api-reference.md +5 -3
- package/docs/architecture.md +3 -0
- package/docs/copy-inventory.md +60 -59
- package/docs/copy-registry.json +72 -63
- package/docs/design/project-startup-direction.md +63 -0
- package/docs/file-map.md +2 -0
- package/docs/module-api-changelog.md +8 -0
- package/docs/page-inventory.json +9 -3
- package/docs/page-readings.json +427 -410
- package/docs/recipes/private-npm-distribution.md +1 -1
- package/migrations/core_269_session_transcripts.sql +35 -0
- package/migrations/core_270_craft_achievements.sql +128 -0
- package/modules/economy/achievements.js +199 -4
- package/modules/economy/credits.js +16 -0
- package/modules/economy/reward.js +3 -0
- package/modules/hall-ui/public/builders.js +1 -1
- package/modules/hall-ui/public/genesis-home.js +17 -3
- package/modules/hall-ui/public/hall-render.js +24 -2
- package/modules/hall-ui/public/index-genesis-demo.states.json +13 -0
- package/modules/hall-ui/public/roster.css +21 -0
- package/modules/hall-ui/public/roster.html +3 -0
- package/modules/hall-ui/public/roster.js +121 -21
- package/modules/hall-ui/public/roster.states.json +26 -1
- package/modules/hall-ui/records/builders-roster.md +20 -0
- package/modules/lifecycle/publish-reconciler.js +25 -0
- package/modules/lifecycle/workflow-dispatch.js +21 -11
- package/modules/npm-release/release.js +10 -1
- package/modules/onboarding/founding-birth.js +116 -0
- package/modules/onboarding/genesis-home.js +72 -5
- package/modules/onboarding/port.js +9 -2
- package/modules/onboarding/routes/onboarding.js +8 -1
- package/modules/provisioning/birth.js +64 -0
- package/modules/provisioning/dns-resolves.js +54 -0
- package/modules/provisioning/migrations/provisioning_036_dns_resolved.sql +30 -0
- package/modules/provisioning/pollers/liveness-sweep.js +5 -0
- package/modules/provisioning/provisioning.js +7 -6
- package/modules/provisioning/routes/provisioning.js +6 -2
- package/modules/provisioning/tests/liveness.mjs +48 -0
- package/modules/public-landing/public/projects-dns-pending.states.json +85 -0
- package/modules/public-landing/public/projects.html +40 -15
- package/modules/sessions/db.js +58 -0
- package/modules/sessions/routes/sessions.js +97 -0
- package/modules/status-ui/public/status.js +39 -7
- package/modules/status-ui/public/style.css +24 -0
- package/modules/ui-design/kit/fixtures/founding-genesis-demo.json +176 -0
- package/modules/ui-design/kit/fixtures/me-founding-demo.json +27 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances-demo.json +3 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances-dns-pending.json +49 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances.json +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +45 -0
- package/scripts/gds/provision.js +77 -53
- package/scripts/gds/session-digest.js +11 -3
- package/scripts/gds/session-transcript-build.js +155 -0
- package/scripts/gds/session-transcript-upload.js +29 -0
- package/scripts/gds/ship-session-upload.js +6 -1
- package/scripts/gds/upgrade-commit-identity.js +32 -0
- package/scripts/gds/upgrade.js +1 -1
- package/scripts/hall-preview/refresh-fixtures.js +2 -0
- package/src/bongos/db.js +13 -0
- package/src/bongos/role-stats.js +36 -2
- package/src/bongos/routes/public.js +13 -2
- package/src/bongos/routes.js +3 -0
- package/src/branding.js +9 -0
- package/src/module-api.js +1 -1
- package/tests/achievements_reconcile.mjs +13 -0
- package/tests/craft_achievements.mjs +349 -0
- package/tests/founding_birth.mjs +200 -0
- package/tests/founding_builders.mjs +2 -1
- package/tests/founding_mode.mjs +3 -1
- package/tests/genesis_home.mjs +71 -2
- package/tests/hall_builders_page_pass.mjs +2 -1
- package/tests/hub_map_orb_states.mjs +5 -1
- package/tests/leaderboard_roles.mjs +272 -0
- package/tests/npm_release_release.mjs +37 -2
- package/tests/projects_hub.mjs +5 -3
- package/tests/projects_hub_dns_ready.mjs +153 -0
- package/tests/projects_hub_pre_uat.mjs +9 -5
- package/tests/provision_dns_first.mjs +171 -0
- package/tests/provision_settings_apply.mjs +6 -1
- package/tests/provisioning_settings_env.mjs +49 -0
- package/tests/session_transcript.mjs +273 -0
- package/tests/ui_design_kit.mjs +1 -1
- package/tests/upgrade_commit_identity.mjs +89 -0
- package/tests/wizard_demo.mjs +29 -3
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// tests/upgrade_commit_identity.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1004514: a landed core move left its pin STAGED but uncommitted whenever the project's
|
|
4
|
+
// git named nobody (no user.name/user.email in the repo or for the account the move runs as).
|
|
5
|
+
// `git commit` refused, persistPin reported "could not commit", the move still landed, and the
|
|
6
|
+
// NEXT move read those leftovers as a dirty tree. Seen live on cloudbongos.com, 2026-10-01.
|
|
7
|
+
//
|
|
8
|
+
// These run persistPin against a REAL repo with every identity source switched off (global
|
|
9
|
+
// config pointed at /dev/null, system config disabled, invented identities refused), so the refusal is git's own. The first
|
|
10
|
+
// test proves the environment really does refuse; without it the fix test could pass vacuously.
|
|
11
|
+
//
|
|
12
|
+
// Run: node --test tests/upgrade_commit_identity.mjs
|
|
13
|
+
|
|
14
|
+
import { test, before, after } from 'node:test';
|
|
15
|
+
import assert from 'node:assert/strict';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
19
|
+
import { tmpdir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const { persistPin } = require('../scripts/gds/upgrade.js');
|
|
24
|
+
const { commitIdentityEnv } = require('../scripts/gds/upgrade-commit-identity.js');
|
|
25
|
+
|
|
26
|
+
// user.useConfigOnly: git on a Mac invents an identity from the hostname, where the box's git
|
|
27
|
+
// refuses; this makes every machine refuse the way the box did.
|
|
28
|
+
const ISOLATE = { GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1',
|
|
29
|
+
GIT_CONFIG_COUNT: '1', GIT_CONFIG_KEY_0: 'user.useConfigOnly', GIT_CONFIG_VALUE_0: 'true' };
|
|
30
|
+
const IDENTITY_VARS = ['GIT_AUTHOR_NAME', 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_NAME', 'GIT_COMMITTER_EMAIL', 'EMAIL'];
|
|
31
|
+
let saved;
|
|
32
|
+
const dirs = [];
|
|
33
|
+
|
|
34
|
+
before(() => {
|
|
35
|
+
saved = Object.fromEntries([...Object.keys(ISOLATE), ...IDENTITY_VARS].map((k) => [k, process.env[k]]));
|
|
36
|
+
Object.assign(process.env, ISOLATE);
|
|
37
|
+
for (const k of IDENTITY_VARS) delete process.env[k];
|
|
38
|
+
});
|
|
39
|
+
after(() => {
|
|
40
|
+
for (const [k, v] of Object.entries(saved)) { if (v === undefined) delete process.env[k]; else process.env[k] = v; }
|
|
41
|
+
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const git = (cwd, ...args) => spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
45
|
+
|
|
46
|
+
// A repo whose first commit carries a one-off identity, then names nobody — the live shape.
|
|
47
|
+
function repoWithPinChange({ identity = null } = {}) {
|
|
48
|
+
const dir = mkdtempSync(join(tmpdir(), 'pin-identity-'));
|
|
49
|
+
dirs.push(dir);
|
|
50
|
+
git(dir, 'init', '-q', '-b', 'main');
|
|
51
|
+
if (identity) { git(dir, 'config', 'user.name', identity.name); git(dir, 'config', 'user.email', identity.email); }
|
|
52
|
+
writeFileSync(join(dir, 'package.json'), '{"dependencies":{"@bongos/core":"1.20.77"}}\n');
|
|
53
|
+
writeFileSync(join(dir, 'package-lock.json'), '{}\n');
|
|
54
|
+
git(dir, '-c', 'user.name=scaffold', '-c', 'user.email=scaffold@example.invalid', 'add', '-A');
|
|
55
|
+
git(dir, '-c', 'user.name=scaffold', '-c', 'user.email=scaffold@example.invalid', 'commit', '-qm', 'scaffold');
|
|
56
|
+
writeFileSync(join(dir, 'package.json'), '{"dependencies":{"@bongos/core":"1.20.78"}}\n');
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const quiet = () => {};
|
|
61
|
+
const pin = (dir) => persistPin({ instanceDir: dir, fromVersion: '1.20.77', toVersion: '1.20.78', commitPin: true, push: false }, spawnSync, quiet, quiet);
|
|
62
|
+
|
|
63
|
+
test('the environment really names nobody: a plain commit is refused', () => {
|
|
64
|
+
const dir = repoWithPinChange();
|
|
65
|
+
git(dir, 'add', 'package.json');
|
|
66
|
+
const r = git(dir, 'commit', '-m', 'probe');
|
|
67
|
+
assert.notEqual(r.status, 0, 'git must refuse here, or the next test proves nothing');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a repo that names nobody still gets its pin committed, signed as the provisioner', () => {
|
|
71
|
+
const dir = repoWithPinChange();
|
|
72
|
+
const r = pin(dir);
|
|
73
|
+
assert.equal(r.committed, true, `persistPin: ${JSON.stringify(r)}`);
|
|
74
|
+
assert.equal(git(dir, 'status', '--porcelain').stdout, '', 'nothing staged or left behind for the next move');
|
|
75
|
+
const who = git(dir, 'log', '-1', '--format=%an <%ae>|%s').stdout.trim();
|
|
76
|
+
assert.match(who, /^Cloud Bongos <bot@[^>]+>\|pin @bongos\/core 1\.20\.78 \(was 1\.20\.77\)$/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('a repo that names someone keeps signing as them', () => {
|
|
80
|
+
const dir = repoWithPinChange({ identity: { name: 'Owner Person', email: 'owner@example.invalid' } });
|
|
81
|
+
assert.equal(commitIdentityEnv(dir, spawnSync), null, 'no fallback when git already has an answer');
|
|
82
|
+
assert.equal(pin(dir).committed, true);
|
|
83
|
+
assert.equal(git(dir, 'log', '-1', '--format=%an <%ae>').stdout.trim(), 'Owner Person <owner@example.invalid>');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('an identity the caller already put in the env is not replaced', () => {
|
|
87
|
+
const dir = repoWithPinChange();
|
|
88
|
+
assert.equal(commitIdentityEnv(dir, spawnSync, { ...process.env, GIT_AUTHOR_EMAIL: 'ci@example.invalid' }), null);
|
|
89
|
+
});
|
package/tests/wizard_demo.mjs
CHANGED
|
@@ -111,14 +111,21 @@ let created = null;
|
|
|
111
111
|
let started = null;
|
|
112
112
|
provisioning.getInstanceBySlug = async () => null;
|
|
113
113
|
provisioning.getInstanceByDomain = async () => null;
|
|
114
|
+
// The order the create does things in (task 1004506): the birth stamp and the demo
|
|
115
|
+
// marker must both be on the row BEFORE the provision intent exists, so the runner's
|
|
116
|
+
// web.env carries them. 'old-one' stands for a project that already existed.
|
|
117
|
+
const order = [];
|
|
118
|
+
const birth = require('../modules/provisioning/birth.js');
|
|
119
|
+
birth.stampBirth = async (_db, id) => { order.push(`birth:${id}`); };
|
|
114
120
|
provisioning.createInstance = async (_db, args) => {
|
|
115
121
|
created = args;
|
|
116
|
-
|
|
122
|
+
const fresh = args.slug !== 'old-one';
|
|
123
|
+
return { instance: { id: 41, owner_builder_id: 7, slug: args.slug, status: fresh ? 'requested' : 'active', hosting_shape: 'byo-host', detail: args.detail || {} }, created: fresh };
|
|
117
124
|
};
|
|
118
|
-
provisioning.enqueueIntent = async (_db, _id, action) => ({ intent: { id: 9, action, state: 'pending' }, created: true }
|
|
125
|
+
provisioning.enqueueIntent = async (_db, _id, action) => { order.push(`enqueue:${action}`); return { intent: { id: 9, action, state: 'pending' }, created: true }; };
|
|
119
126
|
provisioning.getOpenIntent = async () => null;
|
|
120
127
|
provisioning.recordEvent = async () => {};
|
|
121
|
-
demo.startDemoProject = async (_db, _p, id, d) => { started = { id, d }; return null; };
|
|
128
|
+
demo.startDemoProject = async (_db, _p, id, d) => { started = { id, d }; order.push(`demo:${id}`); return null; };
|
|
122
129
|
api.requireBuilder = (req, _res, next) => { req.builder = { id: 7, github_login: 'owner' }; next(); };
|
|
123
130
|
let stored = null;
|
|
124
131
|
provisioning.getInstanceById = async (_db, id) => (id === 41 ? { id: 41, owner_builder_id: 7, demo: stored } : null);
|
|
@@ -158,6 +165,25 @@ test('the create files a normal project marked demo, with the demo\'s picked ans
|
|
|
158
165
|
assert.equal((await post('/provisioning/instances', { slug: 'bad-demo', hosting_shape: 'byo-host', demo: { people: 99, hours_each: 4 } })).status, 400);
|
|
159
166
|
});
|
|
160
167
|
|
|
168
|
+
test('a new project is stamped born, once, before its provision intent; an existing one never is (task 1004506)', async () => {
|
|
169
|
+
order.length = 0;
|
|
170
|
+
assert.equal((await post('/provisioning/instances', { slug: 'born-demo', hosting_shape: 'byo-host', demo: { people: 2, hours_each: 3 } })).status, 201);
|
|
171
|
+
assert.deepEqual(order, ['birth:41', 'demo:41', 'enqueue:provision'], 'the marker and the demo box are on the row before the runner can read it');
|
|
172
|
+
order.length = 0;
|
|
173
|
+
assert.equal((await post('/provisioning/instances', { slug: 'born-real', hosting_shape: 'byo-host' })).status, 201);
|
|
174
|
+
assert.deepEqual(order, ['birth:41', 'enqueue:provision'], 'a real project is born too, with no demo box');
|
|
175
|
+
order.length = 0;
|
|
176
|
+
const again = await post('/provisioning/instances', { slug: 'old-one', hosting_shape: 'byo-host' });
|
|
177
|
+
assert.ok(again.status < 300, JSON.stringify(again.body));
|
|
178
|
+
assert.ok(!order.some((o) => o.startsWith('birth:') || o.startsWith('demo:')), 'a re-request of a project that already exists is never born (spec D8)');
|
|
179
|
+
const keep = birth.stampBirth;
|
|
180
|
+
birth.stampBirth = async () => { throw new Error('db hiccup'); };
|
|
181
|
+
try {
|
|
182
|
+
assert.equal((await post('/provisioning/instances', { slug: 'unlucky', hosting_shape: 'byo-host' })).status, 201,
|
|
183
|
+
'a missed marker costs the project its genesis, never its creation');
|
|
184
|
+
} finally { birth.stampBirth = keep; }
|
|
185
|
+
});
|
|
186
|
+
|
|
161
187
|
test('keep your planet? over the route: the owner answers once', async () => {
|
|
162
188
|
stored = demo.demoRecord({ people: 3, hours_each: 4 }, new Date(Date.now() - 5 * 3600000));
|
|
163
189
|
const r = await post('/provisioning/instances/41/demo', { outcome: 'kept' });
|