@bongos/core 1.21.4 → 1.21.6
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 +233 -108
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -1
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0361-merges-publish-as-candidates-release-decides-what-others-are-offered.md +1 -0
- package/docs/adr/0362-the-governor-docket-is-collected-from-contributed-sources.md +34 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +37 -2
- package/docs/api-reference.md +4 -3
- package/docs/copy-inventory.md +541 -658
- package/docs/copy-registry.json +1205 -2340
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +1 -1
- package/docs/onboarding/diagrams/assertions.json +1 -1
- package/docs/page-inventory.json +47 -28
- package/docs/page-readings.json +1128 -1339
- package/modules/autonomy/config-idle.js +192 -0
- package/modules/autonomy/governor-docket.js +101 -0
- package/modules/autonomy/routes/autonomy.js +27 -0
- package/modules/autonomy/runner-health.js +22 -1
- package/modules/discord/board-broadcast.js +3 -1
- package/modules/discord/craft-broadcast.js +107 -0
- package/modules/discord/routes/discord.js +10 -0
- package/modules/discord/ship-broadcast.js +30 -2
- package/modules/economy/reward.js +3 -0
- package/modules/government/board.js +20 -5
- package/modules/government/docket-vocab.js +82 -0
- package/modules/government/docket.js +458 -0
- package/modules/government/routes/government.js +34 -0
- package/modules/hall-ui/public/city-draw.js +438 -0
- package/modules/hall-ui/public/city.css +110 -0
- package/modules/hall-ui/public/city.html +71 -0
- package/modules/hall-ui/public/city.js +177 -0
- package/modules/hall-ui/public/city.states.json +30 -0
- package/modules/hall-ui/public/gate.js +11 -4
- package/modules/hall-ui/public/genesis-home.js +9 -10
- package/modules/hall-ui/public/profile.css +4 -0
- package/modules/hall-ui/public/profile.js +25 -1
- package/modules/hall-ui/public/settings-autobongos.js +19 -1
- package/modules/hall-ui/public/settings.css +26 -0
- package/modules/hall-ui/public/settings.html +8 -0
- package/modules/hall-ui/public/settings.js +43 -3
- package/modules/ideas/governor-docket.js +93 -0
- package/modules/ideas/routes/blockers.js +5 -0
- package/modules/lifecycle/governor-docket.js +192 -0
- package/modules/lifecycle/routes/gate-approvals.js +78 -33
- package/modules/lifecycle/routes/lifecycle.js +3 -0
- package/modules/onboarding/routes/access-requests.js +68 -27
- package/modules/platform-identity/craft-rollup.js +72 -0
- package/modules/platform-identity/migrations/platform_identity_029_activity_crafts.sql +23 -0
- package/modules/platform-identity/platform-identity.js +12 -6
- package/modules/platform-identity/routes/sso.js +4 -0
- package/modules/platform-identity/tests/platform-identity.mjs +1 -1
- package/modules/provisioning/core-upgrade.js +34 -7
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/seams.js +2 -0
- package/modules/public-landing/public/assets/cosmos.css +6 -50
- package/modules/public-landing/public/projects.html +1170 -1659
- package/modules/public-landing/public/projects.probes.json +18 -18
- package/modules/public-landing/public/projects.states.json +24 -31
- package/modules/security/routes/reports.js +39 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +72 -0
- package/scripts/gds/autobongos-grade-cap.js +187 -0
- package/scripts/gds/autobongos-loop.js +4 -0
- package/scripts/gds/autobongos-run.js +154 -13
- package/scripts/gds/autobongos-verify.js +211 -14
- package/scripts/gds/provision-core-upgrade.js +14 -2
- package/scripts/gds/provision-repo.js +77 -13
- package/scripts/gds/ship-flow.js +5 -0
- package/scripts/gds/ship-preflight-steps.js +2 -1
- package/scripts/gds/ship.js +10 -0
- package/scripts/gds/update-sweep.js +24 -8
- package/scripts/gds/upgrade-outcome.js +1 -0
- package/src/bongos/auth-admission.js +95 -13
- package/src/bongos/db-kernel.js +1 -1
- package/src/bongos/module-rechecks.js +77 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/core-update.js +68 -2
- package/src/bongos/routes/modules.js +17 -0
- package/src/bongos/serve-internal.js +14 -0
- package/src/bongos/software-update.js +3 -2
- package/src/module-api.js +1 -1
- package/tests/activity_rollup_order_db.mjs +3 -1
- package/tests/activity_snapshots_db.mjs +2 -0
- package/tests/autobongos_grade_cap.mjs +125 -0
- package/tests/autobongos_loop.mjs +230 -1
- package/tests/autobongos_verify.mjs +239 -3
- package/tests/autonomy_config_idle.mjs +272 -0
- package/tests/core_update_banner.mjs +29 -0
- package/tests/core_upgrade_door.mjs +31 -1
- package/tests/core_upgrade_runner.mjs +20 -0
- package/tests/discord_craft_broadcast.mjs +162 -0
- package/tests/genesis_home.mjs +1 -1
- package/tests/government_routes.mjs +3 -1
- package/tests/governor_docket.mjs +552 -0
- package/tests/hall_audit.mjs +10 -1
- package/tests/hall_city.mjs +369 -0
- package/tests/hall_page_gate_map.mjs +5 -0
- package/tests/hub_craft_rollup.mjs +350 -0
- package/tests/onboard.mjs +10 -4
- package/tests/platform_boot.mjs +1 -1
- package/tests/profile_rollup_consent.mjs +2 -0
- package/tests/profile_route.mjs +16 -2
- package/tests/projects_hub.mjs +196 -89
- package/tests/projects_hub_app_status.mjs +3 -1
- package/tests/projects_hub_app_step.mjs +72 -48
- package/tests/projects_hub_dns_ready.mjs +10 -2
- package/tests/projects_hub_look.mjs +32 -22
- package/tests/projects_hub_module_picker.mjs +112 -62
- package/tests/projects_hub_pre_uat.mjs +48 -41
- package/tests/provision.mjs +78 -0
- package/tests/public_landing_projects.mjs +12 -6
- package/tests/settings_main_role.mjs +190 -0
- package/tests/software_update.mjs +56 -1
- package/tests/update_subscription_engine.mjs +55 -3
- package/tests/wizard_demo.mjs +37 -11
- package/tests/wizard_draft_resume.mjs +34 -22
- package/tests/wizard_front_door.mjs +173 -103
- package/tests/wizard_intent_resume.mjs +28 -25
- package/tests/wizard_physics.mjs +94 -76
- package/tests/wizard_physics_more.mjs +428 -277
- package/tests/wizard_six_screens.mjs +126 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// tests/projects_hub_module_picker.mjs — the create wizard's module picker,
|
|
2
2
|
// EXECUTED (BV1.R20, task 1002339; goal 1000046 criterion `proj-module-select`).
|
|
3
|
+
// Since task 1004504 the picker is the review's Extras row: its controls moved into the
|
|
4
|
+
// row whole, "Looks right" / "Use this" confirm the set on screen, and there is no Skip —
|
|
5
|
+
// Create waits for every row, this one included.
|
|
3
6
|
//
|
|
4
7
|
// tests/projects_hub.mjs pins this surface statically, on purpose (C7: "real
|
|
5
8
|
// markup, not JS-built"). But the picker is the one part of the wizard whose
|
|
@@ -11,10 +14,12 @@
|
|
|
11
14
|
// The precedent is tests/task_detail_ui.mjs (there is no jsdom in this repo).
|
|
12
15
|
// The difference: task-detail.js is a whole file, while the picker lives inside
|
|
13
16
|
// projects.html's one big inline <script> — so the block is sliced out by its
|
|
14
|
-
// own comment
|
|
15
|
-
// esc, state, saveDraft
|
|
16
|
-
//
|
|
17
|
-
//
|
|
17
|
+
// own comment sentinel (up to the repo picker that follows it) and run with the
|
|
18
|
+
// things it closes over ($, root, esc, state, saveDraft, paintSetup) supplied by the
|
|
19
|
+
// harness. The review's row machinery (ROWS … confirmRow and its delegated click) is
|
|
20
|
+
// sliced out beside it, so confirming the row runs the page's own code. Both slices
|
|
21
|
+
// are assertions: rename an edge and this file fails loudly rather than quietly
|
|
22
|
+
// testing nothing.
|
|
18
23
|
//
|
|
19
24
|
// Run: node --test --test-reporter=tap tests/projects_hub_module_picker.mjs
|
|
20
25
|
|
|
@@ -41,12 +46,21 @@ const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
|
41
46
|
const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8');
|
|
42
47
|
|
|
43
48
|
const START = '/* ── the module picker (task 1002339, BV1.R20) ──';
|
|
44
|
-
const END =
|
|
49
|
+
const END = '/* ── GitHub-integrated repo picker';
|
|
45
50
|
const from = HUB.indexOf(START);
|
|
46
|
-
const to = HUB.indexOf(END);
|
|
51
|
+
const to = HUB.indexOf(END, from);
|
|
47
52
|
assert.ok(from > -1, 'the picker block opens with its sentinel comment');
|
|
48
|
-
assert.ok(to > from, 'the picker block closes
|
|
49
|
-
const PICKER = HUB.slice(from, to
|
|
53
|
+
assert.ok(to > from, 'the picker block closes where the repo picker begins');
|
|
54
|
+
const PICKER = HUB.slice(from, to).replace(/\r\n/g, '\n');
|
|
55
|
+
assert.ok(PICKER.includes('function extrasRow()'), 'and it carries the row text the review reads');
|
|
56
|
+
assert.doesNotMatch(HUB, /id="skipModules"|id="panel-modules"/, 'the old panel and its Skip are gone (task 1004504)');
|
|
57
|
+
// the review's rows: which apply, confirming one, and the delegated click
|
|
58
|
+
const RSTART = ' var ROWS = [';
|
|
59
|
+
const REND = ' /* what each row says: its value';
|
|
60
|
+
const rfrom = HUB.indexOf(RSTART), rto = HUB.indexOf(REND, rfrom);
|
|
61
|
+
assert.ok(rfrom > -1 && rto > rfrom, 'the review rows are sliced by ROWS and rowText');
|
|
62
|
+
const REVIEW = HUB.slice(rfrom, rto).replace(/\r\n/g, '\n');
|
|
63
|
+
assert.ok(REVIEW.includes('function confirmRow(key)'), 'and they carry confirmRow');
|
|
50
64
|
|
|
51
65
|
// The payload GET /provisioning/starter-bundles serves (ADR 0237 + task 1002339).
|
|
52
66
|
// Deliberately a FIXTURE and not a live require: this asserts the picker reads
|
|
@@ -128,9 +142,20 @@ function makeEl(id) {
|
|
|
128
142
|
setAttribute(k, v) { attrs.set(k, String(v)); },
|
|
129
143
|
getAttribute(k) { return attrs.has(k) ? attrs.get(k) : null; },
|
|
130
144
|
addEventListener(type, fn) { this._handlers[type] = fn; },
|
|
145
|
+
focus() {},
|
|
131
146
|
};
|
|
132
147
|
}
|
|
133
148
|
|
|
149
|
+
// One review row, shaped like the markup: its hidden .opts, Looks right and Change.
|
|
150
|
+
function makeRow(key) {
|
|
151
|
+
const row = makeEl('row-' + key);
|
|
152
|
+
row.setAttribute('data-row', key);
|
|
153
|
+
const parts = { '.opts': makeEl('opts-' + key), '[data-row-ok]': makeEl('ok-' + key), '[data-row-chg]': makeEl('chg-' + key) };
|
|
154
|
+
parts['.opts'].hidden = true;
|
|
155
|
+
row.querySelector = (sel) => parts[sel] || null;
|
|
156
|
+
return row;
|
|
157
|
+
}
|
|
158
|
+
|
|
134
159
|
// Parse the rows the picker WROTE, so a selector resolves only when the markup
|
|
135
160
|
// is genuinely there — the point of the harness.
|
|
136
161
|
function rowsOf(html) {
|
|
@@ -159,23 +184,28 @@ function whyOf(html) {
|
|
|
159
184
|
function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
160
185
|
const els = new Map();
|
|
161
186
|
const $ = (id) => { if (!els.has(id)) els.set(id, makeEl(id)); return els.get(id); };
|
|
162
|
-
|
|
163
|
-
const state = { step:
|
|
164
|
-
const
|
|
187
|
+
// step 4 is the review, where the Extras row lives (task 1004504)
|
|
188
|
+
const state = { step: 4, projType: null, teamShape: null, modules: null, confirmed: {}, ...seed };
|
|
189
|
+
const rows = {};
|
|
190
|
+
const rowFor = (sel) => { const m = /data-row="([a-z]+)"/.exec(sel); return m ? (rows[m[1]] = rows[m[1]] || makeRow(m[1])) : null; };
|
|
165
191
|
let saves = 0;
|
|
166
192
|
let fetches = 0;
|
|
167
193
|
let reviews = 0;
|
|
168
194
|
|
|
169
195
|
const sandbox = {
|
|
170
196
|
$,
|
|
171
|
-
root: { querySelector: (sel) => (sel
|
|
197
|
+
root: { querySelector: (sel) => (sel.indexOf('#reviewRows .setRow') === 0 ? rowFor(sel) : null) },
|
|
172
198
|
esc: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])),
|
|
173
199
|
state,
|
|
174
200
|
saveDraft: () => { saves++; },
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
201
|
+
// the block repaints the review's rows when the read lands on step 4 (a resumed
|
|
202
|
+
// draft can reach it having never opened the Extras row)
|
|
203
|
+
paintSetup: () => { reviews++; },
|
|
204
|
+
// the review's other rows close over checks this file does not drive
|
|
205
|
+
physSkips: () => false, appSkipped: () => false, physAnswered: () => true,
|
|
206
|
+
phys: () => ({ answers: { framing: 'managed' } }),
|
|
207
|
+
validateCode: () => true, validateAddress: () => true, validateRender: () => true,
|
|
208
|
+
checkSlugAvailable: () => {}, renderAddrPanel: () => {}, syncHome: () => {}, syncAppStep: () => {}, syncRenderSetup: () => {},
|
|
179
209
|
API: '/api/bongos',
|
|
180
210
|
fetch: () => { fetches++; return Promise.resolve(fetchOk
|
|
181
211
|
? { ok: true, json: () => Promise.resolve(payload) }
|
|
@@ -184,11 +214,17 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
|
184
214
|
};
|
|
185
215
|
sandbox.globalThis = sandbox;
|
|
186
216
|
vm.createContext(sandbox);
|
|
187
|
-
vm.runInContext(PICKER, sandbox, { filename: 'projects.html#module-picker' });
|
|
217
|
+
vm.runInContext(PICKER + '\n' + REVIEW, sandbox, { filename: 'projects.html#module-picker' });
|
|
188
218
|
|
|
189
219
|
const band = $('modCards');
|
|
220
|
+
// a click inside the review, through its one delegated listener, as the browser sends it
|
|
221
|
+
const rowClick = (attr) => {
|
|
222
|
+
const row = rowFor('#reviewRows .setRow[data-row="extras"]');
|
|
223
|
+
const t = { hasAttribute: (a) => a === attr, closest: (sel) => (sel === '.setRow' ? row : null) };
|
|
224
|
+
$('reviewRows')._handlers.click({ target: { closest: () => t } });
|
|
225
|
+
};
|
|
190
226
|
return {
|
|
191
|
-
state,
|
|
227
|
+
state, sandbox,
|
|
192
228
|
saves: () => saves,
|
|
193
229
|
fetches: () => fetches,
|
|
194
230
|
reviews: () => reviews,
|
|
@@ -210,8 +246,12 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
|
210
246
|
};
|
|
211
247
|
band._handlers.click({ target });
|
|
212
248
|
},
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
// "Looks right" on the Extras row, "Change" to open it, "Use this" inside it
|
|
250
|
+
looksRight: () => rowClick('data-row-ok'),
|
|
251
|
+
change: () => rowClick('data-row-chg'),
|
|
252
|
+
use: () => rowClick('data-row-use'),
|
|
253
|
+
confirmed: () => !!(state.confirmed && state.confirmed.extras),
|
|
254
|
+
unconfirmed: () => [...sandbox.unconfirmedRows()],
|
|
215
255
|
forCreate: () => sandbox.modulesForCreate(),
|
|
216
256
|
picked: () => sandbox.pickedModules(),
|
|
217
257
|
// BV1.R21 (task 1002340) — the search box. type() is the real `input` event
|
|
@@ -302,41 +342,42 @@ test('switching every module off is a real answer, not a reset to the bundle', a
|
|
|
302
342
|
assert.deepEqual(h.rows().filter((r) => r.pressed), [], 'and the panel shows it');
|
|
303
343
|
});
|
|
304
344
|
|
|
305
|
-
test('
|
|
345
|
+
test('an unconfirmed row answers NOTHING, and Create waits for it (no Skip since task 1004504)', async () => {
|
|
306
346
|
// Arrange
|
|
307
347
|
const h = boot({ state: { projType: 'game' } });
|
|
308
348
|
await h.open();
|
|
309
349
|
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
assert.
|
|
316
|
-
assert.equal(h.state.modules, null, 'and answers nothing');
|
|
317
|
-
assert.equal(h.forCreate(), undefined);
|
|
318
|
-
assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'while the set they were shown is still the set they get');
|
|
350
|
+
// Assert — the row is shown prefilled, and until it is confirmed nothing is said:
|
|
351
|
+
// the preselect and the server's fallback are the same declaration (ADR 0237)
|
|
352
|
+
assert.equal(h.state.modules, null, 'nothing is answered on the owner\'s behalf');
|
|
353
|
+
assert.equal(h.forCreate(), undefined, 'so a create now would carry no modules key');
|
|
354
|
+
assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'while the set they are shown is still the set they get');
|
|
355
|
+
assert.ok(h.unconfirmed().includes('extras'), 'and the review holds Continue until the row is confirmed');
|
|
319
356
|
});
|
|
320
357
|
|
|
321
|
-
test('
|
|
322
|
-
//
|
|
323
|
-
// meant "I'm done here", not "throw that away".
|
|
358
|
+
test('Change, a toggle, then Use this: the toggle stands as the answer', async () => {
|
|
359
|
+
// A visitor who changed something and then confirmed meant "this", not "throw that away".
|
|
324
360
|
const h = boot({ state: { projType: 'game' } });
|
|
325
361
|
await h.open();
|
|
362
|
+
h.change();
|
|
363
|
+
assert.equal(h.sandbox.$('modCards').innerHTML.length > 0, true, 'opening the row paints the picker');
|
|
326
364
|
h.click('agents');
|
|
327
|
-
h.
|
|
365
|
+
h.use();
|
|
328
366
|
|
|
329
367
|
assert.deepEqual(h.state.modules, ['agents', 'npm-release', 'discord']);
|
|
368
|
+
assert.equal(h.confirmed(), true, 'and the row is confirmed');
|
|
330
369
|
});
|
|
331
370
|
|
|
332
|
-
test('
|
|
371
|
+
test('Looks right CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
|
|
333
372
|
const h = boot({ state: { projType: 'game' } });
|
|
334
373
|
await h.open();
|
|
335
374
|
|
|
336
|
-
h.
|
|
375
|
+
h.looksRight();
|
|
337
376
|
|
|
338
377
|
assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'the preselect becomes the answer');
|
|
339
378
|
assert.deepEqual(h.forCreate(), ['npm-release', 'discord'], 'and rides the create request');
|
|
379
|
+
assert.equal(h.confirmed(), true);
|
|
380
|
+
assert.ok(!h.unconfirmed().includes('extras'), 'and Create no longer waits on it');
|
|
340
381
|
});
|
|
341
382
|
|
|
342
383
|
test('changing the project type re-preselects while nothing has been said, and never after', async () => {
|
|
@@ -376,12 +417,18 @@ test('a catalog that will not load degrades to a clause — it never blocks a pr
|
|
|
376
417
|
assert.deepEqual(h.rows(), [], 'no rows are invented');
|
|
377
418
|
assert.match(h.coreNote(), /couldn’t load/, 'the panel says so in the owner\'s words');
|
|
378
419
|
assert.equal(h.state.modules, null, 'nothing is answered on their behalf');
|
|
379
|
-
|
|
380
|
-
|
|
420
|
+
// The row still has to be confirmable, or a failed read would block Create. But the row
|
|
421
|
+
// said "the suggested set", so confirming it must not file an EMPTY list — that would
|
|
422
|
+
// switch off the very bundle the server would otherwise apply.
|
|
423
|
+
h.looksRight();
|
|
424
|
+
assert.equal(h.confirmed(), true, 'the wizard still walks on');
|
|
425
|
+
// (through JSON: a vm-born array never strictly equals one born here, so a bare
|
|
426
|
+
// notDeepEqual would pass whatever the page did)
|
|
427
|
+
assert.notEqual(JSON.stringify(h.forCreate()), '[]', 'and confirming "the suggested set" never files "none"');
|
|
381
428
|
});
|
|
382
429
|
|
|
383
430
|
test('every rendered value goes through the page escaper', async () => {
|
|
384
|
-
// The rows are built with innerHTML (
|
|
431
|
+
// The rows are built with innerHTML (the old review ledger's idiom in this
|
|
385
432
|
// file), so the escaping is the safety property and it is asserted, not assumed.
|
|
386
433
|
const h = boot({ state: { projType: 'game' } });
|
|
387
434
|
await h.open();
|
|
@@ -410,12 +457,12 @@ test('the catalog read is LAZY and asked once — /projects serves four views fr
|
|
|
410
457
|
assert.equal(h.fetches(), 1, 'entering the step asks exactly once, however often it is entered');
|
|
411
458
|
});
|
|
412
459
|
|
|
413
|
-
test('the
|
|
460
|
+
test('the Extras row names the set even when the picker was never opened (task 1002339)', async () => {
|
|
414
461
|
// A resumed draft lands straight on the review, so the catalog has not loaded
|
|
415
462
|
// and the module NAMES are not known. "None — just the core" would be a false
|
|
416
|
-
// promise
|
|
417
|
-
//
|
|
418
|
-
const h = boot({ state: { projType: 'game', step:
|
|
463
|
+
// promise before Create: the server is about to apply the type's bundle. The row
|
|
464
|
+
// says that instead, then repaints when the read lands.
|
|
465
|
+
const h = boot({ state: { projType: 'game', step: 4 } }); // 4 = the review since task 1004504 (12 since 1004418)
|
|
419
466
|
|
|
420
467
|
// Arrange + Act — the review asks for the value before the read can answer
|
|
421
468
|
const first = h.extrasRow();
|
|
@@ -425,27 +472,31 @@ test('the review names the set even when the picker was never opened (task 10023
|
|
|
425
472
|
assert.equal(h.fetches(), 1, 'and asking for it is what starts the read');
|
|
426
473
|
|
|
427
474
|
await h.settle();
|
|
428
|
-
assert.equal(h.reviews(), 1, 'the review repaints
|
|
475
|
+
assert.equal(h.reviews(), 1, 'the review repaints its rows once the names are known');
|
|
429
476
|
assert.equal(h.extrasRow(), 'npm releases, Discord', 'and then it names them');
|
|
477
|
+
const elsewhere = boot({ state: { projType: 'game', step: 3 } });
|
|
478
|
+
elsewhere.extrasRow();
|
|
479
|
+
await elsewhere.settle();
|
|
480
|
+
assert.equal(elsewhere.reviews(), 0, 'off the review, a landing read repaints no rows');
|
|
430
481
|
});
|
|
431
482
|
|
|
432
|
-
test('the
|
|
433
|
-
const chosen = boot({ state: { projType: 'game', modules: ['agents'], step:
|
|
483
|
+
test('the Extras row reads an answered picker back, empty included', async () => {
|
|
484
|
+
const chosen = boot({ state: { projType: 'game', modules: ['agents'], step: 3 } });
|
|
434
485
|
// before the catalog lands the row says the true thing without the names —
|
|
435
486
|
// never the raw module keys, which are jargon on an owner-facing screen
|
|
436
487
|
assert.equal(chosen.extrasRow(), 'The extras you picked');
|
|
437
488
|
await chosen.settle();
|
|
438
489
|
assert.equal(chosen.extrasRow(), 'Extra agents', 'and names them once it has');
|
|
439
490
|
|
|
440
|
-
const none = boot({ state: { projType: 'game', modules: [], step:
|
|
491
|
+
const none = boot({ state: { projType: 'game', modules: [], step: 3 } });
|
|
441
492
|
none.extrasRow();
|
|
442
493
|
await none.settle();
|
|
443
494
|
assert.equal(none.extrasRow(), 'None — just the core', 'a deliberate "none" is stated as one');
|
|
444
495
|
});
|
|
445
496
|
|
|
446
497
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
447
|
-
// BV1.R22 (task 1002341) — the About step's answer
|
|
448
|
-
// the
|
|
498
|
+
// BV1.R22 (task 1002341) — the team shape (the About step's answer, the review's
|
|
499
|
+
// Team size row since task 1004504) reaches the preselect, and the row says why. ADR 0243.
|
|
449
500
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
450
501
|
|
|
451
502
|
test('the team shape adjusts the type bundle, and the panel says why in the owner\'s words', async () => {
|
|
@@ -487,9 +538,9 @@ test('an added module is preselected and explained', async () => {
|
|
|
487
538
|
assert.deepEqual(h.why(), ['You said an open community is building this, so somewhere for it to gather is switched on.']);
|
|
488
539
|
});
|
|
489
540
|
|
|
490
|
-
test('
|
|
491
|
-
// The
|
|
492
|
-
//
|
|
541
|
+
test('changing the team shape later re-preselects, with no second read', async () => {
|
|
542
|
+
// The kind and the team are both changeable. The whole table arrived in one read
|
|
543
|
+
// precisely so a change costs nothing.
|
|
493
544
|
const h = boot({ state: { projType: 'research' }, payload: TAILORED });
|
|
494
545
|
await h.open();
|
|
495
546
|
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents'], 'unanswered: the raw bundle');
|
|
@@ -501,19 +552,18 @@ test('answering the About step later re-preselects, with no second read', async
|
|
|
501
552
|
assert.equal(h.fetches(), 1, 'and the answer cost no round trip');
|
|
502
553
|
});
|
|
503
554
|
|
|
504
|
-
test('
|
|
555
|
+
test('after a team answer the unconfirmed row still answers nothing, and confirming takes the tailored set', async () => {
|
|
505
556
|
// ADR 0240 §2's property, extended to the interview's answer (ADR 0243): the
|
|
506
|
-
// preselect and the server's fallback are the same declaration, so
|
|
507
|
-
// must still answer NOTHING and
|
|
557
|
+
// preselect and the server's fallback are the same declaration, so an unconfirmed
|
|
558
|
+
// row must still answer NOTHING, and confirming it must take the tailored set.
|
|
508
559
|
const h = boot({ state: { projType: 'game', teamShape: 'solo' }, payload: TAILORED });
|
|
509
560
|
await h.open();
|
|
510
561
|
|
|
511
|
-
h.
|
|
512
|
-
|
|
513
|
-
assert.deepEqual(h.shown, [8]);
|
|
514
|
-
assert.equal(h.state.modules, null, 'skipping answers nothing, tailored or not');
|
|
515
|
-
assert.equal(h.forCreate(), undefined, 'so the create request still carries no modules key');
|
|
562
|
+
assert.equal(h.state.modules, null, 'unconfirmed answers nothing, tailored or not');
|
|
563
|
+
assert.equal(h.forCreate(), undefined, 'so the create request would carry no modules key');
|
|
516
564
|
assert.deepEqual(h.picked(), ['discord'], 'while the set they were shown is still the set they get');
|
|
565
|
+
h.looksRight();
|
|
566
|
+
assert.deepEqual(h.forCreate(), ['discord'], 'and confirming files exactly that set');
|
|
517
567
|
});
|
|
518
568
|
|
|
519
569
|
test('once the owner has answered, the reasons go — the panel stops arguing', async () => {
|
|
@@ -820,8 +870,8 @@ test('with nothing switched on the count says only what is shown', async () => {
|
|
|
820
870
|
assert.equal(h.filterNote(), 'Showing 3 of 3.', 'no "0 extras are switched on" noise');
|
|
821
871
|
});
|
|
822
872
|
|
|
823
|
-
test('the
|
|
824
|
-
const h = boot({ state: { projType: 'game', step:
|
|
873
|
+
test('the Extras row names the hidden picks too — a filter never reaches the create request', async () => {
|
|
874
|
+
const h = boot({ state: { projType: 'game', step: 4 } }); // 4 = the review since task 1004504 (12 since 1004418)
|
|
825
875
|
await h.open();
|
|
826
876
|
h.click('agents');
|
|
827
877
|
assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord']);
|
|
@@ -229,14 +229,21 @@ test('the wizard’s 401 recoveries keep the repo scope they already granted', (
|
|
|
229
229
|
assert.match(fn('signInAgain'), /currentView === 'new' \? '\/projects%3Fview%3Dnew&repos=1'/, 'signInAgain from the wizard');
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
-
test('a resumed draft past
|
|
232
|
+
test('a resumed draft past the code row still probes repo access before Create', () => {
|
|
233
|
+
// task 1004504: the Home step is the review's code row, so the review probes on entry —
|
|
234
|
+
// and a draft that resumes on the look screen (past the review, before done) must still
|
|
235
|
+
// probe, or Create files a repo it promised to make but never did (ensureFreshRepo skips
|
|
236
|
+
// the create whenever repoAccess is not true)
|
|
237
|
+
const REVIEW = STEP['panel-review'];
|
|
238
|
+
assert.match(SCRIPT, new RegExp(`if \\(step === ${REVIEW}\\) \\{[\\s\\S]*?if \\(authKnown\\) ensureRepoAccess\\(\\);`),
|
|
239
|
+
'the review screen probes on entry');
|
|
233
240
|
assert.ok(SCRIPT.includes(
|
|
234
|
-
`else if (step > ${
|
|
235
|
-
'every
|
|
241
|
+
`else if (step > ${REVIEW} && step < ${DONE} && authKnown && repoAccess === null) ensureRepoAccess();`),
|
|
242
|
+
'every screen between the review and the done panel re-probes');
|
|
236
243
|
});
|
|
237
244
|
|
|
238
|
-
test('a half-typed custom domain says why
|
|
239
|
-
assert.match(SCRIPT, /That doesn’t look like a domain yet \(e\.g\.
|
|
245
|
+
test('a half-typed custom domain says why Use this is dead; a taken handle silences the format cheer', () => {
|
|
246
|
+
assert.match(SCRIPT, /That doesn’t look like a domain yet \(e\.g\. moonbase\.example\.com\) — fix it, or clear the field to use the address above\./);
|
|
240
247
|
assert.match(SCRIPT, /if \(avail === 'taken' \|\| avail === 'yours' \|\| avail === 'reserved'\) \{ var hs = \$\('h-slug'\); hs\.innerHTML = ' '; hs\.className = 'hint'; \}/);
|
|
241
248
|
});
|
|
242
249
|
|
|
@@ -245,12 +252,14 @@ test('no wall-clock promises, no operator vocabulary on the stranger’s path',
|
|
|
245
252
|
assert.doesNotMatch(HUB, /about five minutes/, 'the empty state');
|
|
246
253
|
assert.doesNotMatch(HUB, /a runner does the real work/, 'the mine fine print');
|
|
247
254
|
assert.doesNotMatch(SCRIPT, /pinned platform core/, 'the review Shape row');
|
|
248
|
-
/* Since ADR 0345 (task 1004351) the
|
|
249
|
-
|
|
250
|
-
owner vocabulary, and no wall-clock promise. */
|
|
251
|
-
assert.
|
|
255
|
+
/* Since ADR 0345 (task 1004351) the APP has its own row; since task 1004504 the review is
|
|
256
|
+
the row-by-row confirm, and that row speaks the owner's words. The assertion still
|
|
257
|
+
guards the same property this test is about: owner vocabulary, and no wall-clock promise. */
|
|
258
|
+
assert.match(HUB, /<div class="setRow" data-row="app">\s*<span class="k">App hosting<\/span>/, 'the review names the app\'s hosting in plain words');
|
|
259
|
+
assert.ok(SCRIPT.includes("return { v: appPlatformChoice() === 'render' ? 'Render' : 'Decide later', s: appRowDetail() };"), 'its value is a place or "Decide later", never a release name');
|
|
260
|
+
assert.doesNotMatch(SCRIPT, /current release|release channel/, 'no release vocabulary on the stranger\'s path');
|
|
252
261
|
assert.doesNotMatch(HUB, /\/new-project<\/code> assistant/, 'the done panel’s closing line');
|
|
253
|
-
assert.match(HUB, /
|
|
262
|
+
assert.match(HUB, /[Ss]ign-in switches on after the GitHub steps|switch sign-in on/, 'the address step does not promise sign-in "just works"');
|
|
254
263
|
assert.doesNotMatch(HUB, /Sign-in, sharing, everything just works/);
|
|
255
264
|
});
|
|
256
265
|
|
|
@@ -465,10 +474,19 @@ test('decision 5 — the Type step says where the type can be changed (task 1003
|
|
|
465
474
|
// for now", because no owner-facing route could change a declared type. Task 1003504
|
|
466
475
|
// built that route and the manage page's "Its kind" picker on it, so the sentence is
|
|
467
476
|
// earned back — and it now has to name WHERE, or it is a promise with no address.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
assert.doesNotMatch(
|
|
477
|
+
// task 1004504 folded the Type step into "What are you making?" as its Kind field, in the
|
|
478
|
+
// design of record's words, which make no promise about changing it. What must hold: the
|
|
479
|
+
// narrowed promise never comes back, and any change-later promise carries its address.
|
|
480
|
+
assert.doesNotMatch(HUB, /id="panel-type"/, 'the Type step is gone');
|
|
481
|
+
const what = (HUB.match(/<section class="panel" id="panel-what"[\s\S]*?<\/section>/) || [''])[0];
|
|
482
|
+
assert.ok(what, '#panel-what exists');
|
|
483
|
+
assert.match(what, /<span id="typeLbl">Kind<\/span>/, 'the kind is asked on the What screen');
|
|
484
|
+
assert.doesNotMatch(what, /stays as you pick it/, 'the narrowed promise must not survive');
|
|
485
|
+
if (/change it later/.test(what)) {
|
|
486
|
+
assert.match(what, /change it later from the project’s own page/, 'a change-later promise names WHERE — a promise with an address');
|
|
487
|
+
}
|
|
488
|
+
// and the address it would name is real: the manage page's "Its kind" picker
|
|
489
|
+
assert.match(HUB, /<h2>Its kind<\/h2>/, 'the project’s own page carries the kind picker');
|
|
472
490
|
});
|
|
473
491
|
|
|
474
492
|
test('decision 8 — a card carrying a red error paragraph never tallies under Live', () => {
|
|
@@ -542,23 +560,17 @@ test('a manifest error says a plain sentence, folds the route text, and sends a
|
|
|
542
560
|
assert.equal((SCRIPT.match(/manifestMsg: '', manifestCode: '', appSlug: null/g) || []).length, 2, 'both resets carry the code');
|
|
543
561
|
});
|
|
544
562
|
|
|
545
|
-
test('the rail
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
assert.
|
|
550
|
-
|
|
551
|
-
assert.
|
|
552
|
-
|
|
553
|
-
assert.match(
|
|
554
|
-
|
|
555
|
-
assert.
|
|
556
|
-
'the done-panel poll repaints when the status moves');
|
|
557
|
-
// the first NUMBERED beat carries it — since task 1004417 the front door's two screens come
|
|
558
|
-
// before the rail, and the card statically wears wizFront so a no-script visitor sees no rail there
|
|
559
|
-
// (since task 1004418 that beat is planet physics, the first numbered step)
|
|
560
|
-
assert.match(HUB, new RegExp(`<li class="now" data-step="${STEP['panel-physics']}" aria-current="step"><span class="snum">1</span>`), 'the static rail starts with the same marker');
|
|
561
|
-
assert.match(HUB, /<section id="view-new" hidden>\s*<div class="wrap viewWrap">\s*<div class="card wizFront">/, 'and steps aside on the static first screen');
|
|
563
|
+
test('the progress rail is gone (task 1004504): no painter, no repaint at the done panel, no static rail', () => {
|
|
564
|
+
// task 1003244 kept the rail's marker on Create until the project answered; task 1004504
|
|
565
|
+
// retired the rail outright, so nothing on the page may still paint or carry one
|
|
566
|
+
assert.doesNotMatch(SCRIPT, /function paintRail|paintRail\(/, 'no rail painter, and no caller');
|
|
567
|
+
assert.doesNotMatch(fn('show'), /paintRail|classList\.toggle\('done'/, 'show() marks no rail of any kind');
|
|
568
|
+
assert.doesNotMatch(HUB, /data-step="\d+"/, 'no static rail beat ships in the markup');
|
|
569
|
+
assert.doesNotMatch(HUB, /<ol class="steps"/, 'no rail list ships in the markup');
|
|
570
|
+
// the night room sits behind the card; the card opens on the eyebrow and the one Back, then the wizard
|
|
571
|
+
assert.match(HUB, /<section id="view-new" hidden>[\s\S]*?<\/div>\s*<div class="wrap viewWrap">\s*<div class="card[^"]*">\s*(?:<!--[\s\S]*?-->\s*)?<div class="wizTop">[\s\S]*?<\/div>\s*<div class="createSplit">/, 'the card opens on its top row, then straight on the wizard');
|
|
572
|
+
const head = HUB.slice(HUB.indexOf('<section id="view-new"'), HUB.indexOf('<div class="createSplit">'));
|
|
573
|
+
assert.doesNotMatch(head, /class="steps|data-step=/, 'no rail between the room and the wizard');
|
|
562
574
|
});
|
|
563
575
|
|
|
564
576
|
test('every action result announces, nothing under the pointer is deleted, and focus stays where it was', () => {
|
|
@@ -682,16 +694,11 @@ test('the hub has one main landmark, a heading for the map, a footer, and a skip
|
|
|
682
694
|
assert.doesNotMatch(HUB, /<main class="card">/, 'the old inner main is a div');
|
|
683
695
|
});
|
|
684
696
|
|
|
685
|
-
test('
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
assert.
|
|
689
|
-
|
|
690
|
-
assert.match(SCRIPT, /railEl\.addEventListener\('click'/);
|
|
691
|
-
assert.match(SCRIPT, /railEl\.addEventListener\('keydown'/);
|
|
692
|
-
assert.match(SCRIPT, /show\(Number\(li\.getAttribute\('data-goto'\)\), true\)/, 'a deliberate step: focus moves');
|
|
693
|
-
assert.match(COSMOS, /\.steps li\[role=button\]\{cursor:pointer;\}/);
|
|
694
|
-
assert.match(COSMOS, /\.steps li\[role=button\]:focus-visible\{outline:2px solid var\(--accent\);/);
|
|
697
|
+
test('the rail\'s way-back handlers left with it, the repo picker is honest ARIA, the card inerts the page', () => {
|
|
698
|
+
// task 1003246 made answered rail steps a way back; task 1004504 retired the rail, so its
|
|
699
|
+
// delegated handlers and their CSS must not linger as dead code
|
|
700
|
+
assert.doesNotMatch(SCRIPT, /railEl|data-goto/, 'no delegated rail handler is left');
|
|
701
|
+
assert.doesNotMatch(COSMOS, /\.steps li\[role=button\]/, 'and no rail-button CSS');
|
|
695
702
|
assert.doesNotMatch(HUB, /role="listbox"/);
|
|
696
703
|
assert.doesNotMatch(SCRIPT, /role="option"/);
|
|
697
704
|
assert.match(HUB, /<div id="ghList" class="ghlist" role="group" aria-label="Your repositories"><\/div>/, 'the label survives the listbox role');
|
package/tests/provision.mjs
CHANGED
|
@@ -1074,6 +1074,84 @@ await ta('scaffoldStandaloneRepo: a REGISTRY-pinned consumer control plane PACKS
|
|
|
1074
1074
|
}
|
|
1075
1075
|
});
|
|
1076
1076
|
|
|
1077
|
+
// ── new projects start on the RELEASED core, not the hall's candidate (task 1004299) ──
|
|
1078
|
+
// ADR 0361: merges publish as candidates and the platform hall runs them, so the core
|
|
1079
|
+
// this hall runs is routinely AHEAD of the registry's `latest` label. A new project —
|
|
1080
|
+
// and the pin refresh that runs in the same provisioning pass — must get the release.
|
|
1081
|
+
t('projectCoreDecision: the hall ahead of the release → the release; level or unreadable → the running core', () => {
|
|
1082
|
+
assert.deepEqual(
|
|
1083
|
+
(({ version, source }) => ({ version, source }))(RP.projectCoreDecision({ running: '1.19.60', released: '1.19.50' })),
|
|
1084
|
+
{ version: '1.19.50', source: 'released' });
|
|
1085
|
+
assert.equal(RP.projectCoreDecision({ running: '1.19.50', released: '1.19.50' }).source, 'running', 'level → byte-exact running core, no registry pack');
|
|
1086
|
+
assert.equal(RP.projectCoreDecision({ running: '1.19.40', released: '1.19.50' }).version, '1.19.40', 'never moves a project PAST what the hall has run');
|
|
1087
|
+
assert.equal(RP.projectCoreDecision({ running: '1.19.60', released: null }).version, '1.19.60', 'no label → fail-open to the running core');
|
|
1088
|
+
assert.equal(RP.projectCoreDecision({ running: '1.19.60', released: '1.19.50-rc.1' }).source, 'running', 'a prerelease label is not a release');
|
|
1089
|
+
assert.equal(RP.projectCoreDecision({ running: '1.19.60', released: '1.19.50;touch /tmp/x' }).source, 'running', 'a non-semver label never reaches a shell');
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
// A registry-installed control plane at 1.19.60 whose registry says `latest` = 1.19.50.
|
|
1093
|
+
function candidateHall({ latest }) {
|
|
1094
|
+
const cp = mkdtempSync(join(tmpdir(), 'cpdir-cand-'));
|
|
1095
|
+
mkdirSync(join(cp, 'node_modules', '@bongos', 'core'), { recursive: true });
|
|
1096
|
+
writeFileSync(join(cp, 'node_modules', '@bongos', 'core', 'package.json'), JSON.stringify({ name: '@bongos/core', version: '1.19.60' }));
|
|
1097
|
+
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': '1.19.60' } }));
|
|
1098
|
+
const { deps, cmds } = recorder({ apply: true });
|
|
1099
|
+
const exec = (cmd, o) => { cmds.push(cmd); return /^npm view @bongos\/core dist-tags\.latest$/.test(cmd) ? { ok: true, stdout: `${latest}\n` } : { ok: true, stdout: '' }; };
|
|
1100
|
+
return { cp, cmds, deps: { ...deps, exec, instanceRoot: cp } };
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
await ta('scaffoldStandaloneRepo: a hall running an UNRELEASED candidate births the project on the released core (task 1004299)', async () => {
|
|
1104
|
+
const { cp, cmds, deps } = candidateHall({ latest: '1.19.50' });
|
|
1105
|
+
const base = mkdtempSync(join(tmpdir(), 'cpdir-standalone-'));
|
|
1106
|
+
const origBase = P.CONFIG.standaloneBase;
|
|
1107
|
+
P.CONFIG.standaloneBase = base;
|
|
1108
|
+
try {
|
|
1109
|
+
await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' }, deps);
|
|
1110
|
+
const joined = cmds.join('\n');
|
|
1111
|
+
assert.match(joined, /npm pack @bongos\/core@1\.19\.50 --pack-destination /, 'packs the RELEASED version from the registry');
|
|
1112
|
+
assert.match(joined, /--vendor-core \S*bongos-core-1\.19\.50\.tgz/, 'the child is pinned to the release');
|
|
1113
|
+
assert.ok(!/1\.19\.60\.tgz|bongos-core-pack-1\.19\.60/.test(joined), 'the candidate the hall runs is never vended');
|
|
1114
|
+
assert.match(joined, /on a pinned @bongos\/core 1\.19\.50 /, 'the scaffold commit names the version it really pinned');
|
|
1115
|
+
assert.ok(joined.indexOf('rm -rf') > joined.indexOf('--vendor-core'), 'the temp pack dir is cleaned after init');
|
|
1116
|
+
} finally {
|
|
1117
|
+
P.CONFIG.standaloneBase = origBase;
|
|
1118
|
+
[cp, base].forEach((d) => rmSync(d, { recursive: true, force: true }));
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
await ta('scaffoldStandaloneRepo: a hall running the release itself keeps the byte-exact running core (task 1004299)', async () => {
|
|
1123
|
+
const { cp, cmds, deps } = candidateHall({ latest: '1.19.60' });
|
|
1124
|
+
const base = mkdtempSync(join(tmpdir(), 'cpdir-standalone-'));
|
|
1125
|
+
const origBase = P.CONFIG.standaloneBase;
|
|
1126
|
+
P.CONFIG.standaloneBase = base;
|
|
1127
|
+
try {
|
|
1128
|
+
await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' }, deps);
|
|
1129
|
+
const joined = cmds.join('\n');
|
|
1130
|
+
assert.ok(!/npm pack @bongos\/core@/.test(joined), 'no registry pack when nothing is unreleased');
|
|
1131
|
+
assert.match(joined, /--vendor-core \S*bongos-core-1\.19\.60\.tgz/);
|
|
1132
|
+
} finally {
|
|
1133
|
+
P.CONFIG.standaloneBase = origBase;
|
|
1134
|
+
[cp, base].forEach((d) => rmSync(d, { recursive: true, force: true }));
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
await ta('refreshStandaloneCorePin: a project pinned to the release is NOT bumped to the hall\'s candidate in the same run (task 1004299)', async () => {
|
|
1139
|
+
const { cp, cmds, deps } = candidateHall({ latest: '1.19.50' });
|
|
1140
|
+
const base = mkdtempSync(join(tmpdir(), 'cpdir-standalone-'));
|
|
1141
|
+
const origBase = P.CONFIG.standaloneBase;
|
|
1142
|
+
P.CONFIG.standaloneBase = base;
|
|
1143
|
+
try {
|
|
1144
|
+
mkdirSync(join(base, 'demo'));
|
|
1145
|
+
writeFileSync(join(base, 'demo', 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': 'file:vendor/bongos-core-1.19.50.tgz' } }));
|
|
1146
|
+
const res = await P.refreshStandaloneCorePin({ slug: 'demo', hosting_shape: 'cloud-host', target_ref: 'example-owner/demo' }, deps);
|
|
1147
|
+
assert.equal(res.action, 'current', 'the released pin is already the core this project is given');
|
|
1148
|
+
assert.ok(!cmds.some((c) => /npm pack/.test(c)), 'nothing is packed or re-vendored');
|
|
1149
|
+
} finally {
|
|
1150
|
+
P.CONFIG.standaloneBase = origBase;
|
|
1151
|
+
[cp, base].forEach((d) => rmSync(d, { recursive: true, force: true }));
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1077
1155
|
// ── scaffold push via the owner OAuth token (task 1002317) ────────────────────────
|
|
1078
1156
|
// Deploy keys are per-repo; a shared key can't write to a NEW project repo. Push with the
|
|
1079
1157
|
// owner's token (which created the repo) instead — WITHOUT leaking it into a logged command.
|
|
@@ -327,7 +327,8 @@ test('the create-repo response keeps the html_url it already returned', async ()
|
|
|
327
327
|
|
|
328
328
|
test('the no-address opt-out is visible at the step, not folded under "a domain I own" (N8)', async () => {
|
|
329
329
|
const { body } = await request('/projects', 'game.test.local');
|
|
330
|
-
|
|
330
|
+
// since task 1004504 the address step is the review's Address row; the box moved in whole
|
|
331
|
+
const panel = (body.match(/<div class="addrConfirm" id="addrConfirm">([\s\S]*?)\n {22}<\/div>/) || [, ''])[1];
|
|
331
332
|
assert.ok(panel, 'the address confirmation box must be extractable');
|
|
332
333
|
const fold = (panel.match(/<details id="addrCustom">[\s\S]*?<\/details>/) || [''])[0];
|
|
333
334
|
assert.ok(fold, 'the bring-your-own-domain disclosure still exists');
|
|
@@ -338,8 +339,10 @@ test('the no-address opt-out is visible at the step, not folded under "a domain
|
|
|
338
339
|
'the opt-out sits after the disclosure, in the open');
|
|
339
340
|
// all three choices legible at once: assigned address, own domain, no address
|
|
340
341
|
assert.match(panel, /id="addrAssigned"/);
|
|
341
|
-
assert.match(fold,
|
|
342
|
-
assert.match(panel, /
|
|
342
|
+
assert.match(fold, /<summary>A domain I own<\/summary>/);
|
|
343
|
+
assert.match(panel, /No address yet\. Sign-in can’t be switched on until it has one\./,
|
|
344
|
+
'the opt-out is offered in the open, and says what going without costs');
|
|
345
|
+
assert.match(body, /<div class="setRow" data-row="address">[\s\S]*?id="addrConfirm"/, 'and the box lives on the review\'s Address row');
|
|
343
346
|
});
|
|
344
347
|
|
|
345
348
|
test('the ownership promise is gated on the availability answer, never on format (N10)', async () => {
|
|
@@ -375,9 +378,12 @@ test('the availability check is debounced, generation-guarded, and fails to "unk
|
|
|
375
378
|
|
|
376
379
|
test('only a DEFINITE negative blocks the step — unknown and checking never gate it (N10)', async () => {
|
|
377
380
|
const { body } = await request('/projects', 'game.test.local');
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
+
// since task 1004504 the address step's Next check is validateAddress(), run when the
|
|
382
|
+
// review's Address row is confirmed
|
|
383
|
+
const fn = (body.match(/function validateAddress\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
384
|
+
assert.ok(fn, 'validateAddress() must be extractable');
|
|
385
|
+
assert.match(body, /if \(key === 'address'\) return validateAddress\(\);/, 'and the Address row cannot be confirmed past it');
|
|
386
|
+
const gate = (fn.match(/if \(!state\.domain && !state\.noAddress\) \{[\s\S]*?\r?\n {6}\}/) || [''])[0];
|
|
381
387
|
assert.ok(gate, 'the availability gate must be extractable');
|
|
382
388
|
assert.match(gate, /av === 'taken' \|\| av === 'yours' \|\| av === 'reserved'/,
|
|
383
389
|
'exactly the three definite negatives block');
|