@bongos/core 1.24.69 → 1.24.71
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 +44 -29
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +4 -2
- package/docs/copy-inventory.md +36 -32
- package/docs/copy-registry.json +86 -50
- package/docs/module-api-changelog.md +4 -2
- package/docs/page-readings.json +7 -7
- package/modules/hall-ui/public/settings.js +49 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +15 -1
- package/scripts/bongos/docs-entropy.js +4 -1
- package/scripts/bongos/fitness-lib.js +19 -1
- package/scripts/bongos/paste-token.js +75 -4
- package/scripts/bongos/provision-repo.js +117 -6
- package/scripts/bongos/ship-check.js +11 -3
- package/src/bongos/device-flow-blocker.js +56 -0
- package/src/bongos/routes/auth.js +5 -0
- package/src/module-api.js +1 -1
- package/tests/bongos_cli.mjs +2 -1
- package/tests/cli_token_reissue.mjs +4 -2
- package/tests/hall_onboarding_bongos_setup.mjs +3 -1
- package/tests/hall_settings_world.mjs +2 -2
- package/tests/paste_token_windows.mjs +165 -0
- package/tests/provision.mjs +132 -7
- package/tests/vendored_work_root.mjs +72 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// tests/paste_token_windows.mjs — a first-time CLI sign-in on Windows took four failed
|
|
2
|
+
// attempts (task 1005033). Three of the four failure points are in code:
|
|
3
|
+
//
|
|
4
|
+
// 2. The hall's one-liner starts with `npx`. In Windows PowerShell `npx` resolves to
|
|
5
|
+
// npx.ps1, which the DEFAULT execution policy refuses to run ("running scripts is
|
|
6
|
+
// disabled on this system"). `npx.cmd` is the same program without the script
|
|
7
|
+
// host, and works in PowerShell and cmd alike. The hall now hands a Windows
|
|
8
|
+
// browser the npx.cmd form.
|
|
9
|
+
// 3. The one-liner only resolves `bongos` inside the project folder (npx finds the
|
|
10
|
+
// project's own bin); from anywhere else npm says "could not determine executable
|
|
11
|
+
// to run". Nothing said so — the hall now says it in the instruction itself.
|
|
12
|
+
// 4. The token sat on the command line (shell history). paste-token now also reads it
|
|
13
|
+
// from a hidden prompt, or from stdin when piped, when no token argument is given.
|
|
14
|
+
//
|
|
15
|
+
// 1. The App's Device Flow switch was off. GitHub has no API to tick it, so the fix is
|
|
16
|
+
// telling the OWNER: the first refusal files one idempotent blocker
|
|
17
|
+
// (src/bongos/device-flow-blocker.js). Task 1005118 later replaces the paste route
|
|
18
|
+
// with a browser approval.
|
|
19
|
+
//
|
|
20
|
+
// Run: node --test tests/paste_token_windows.mjs
|
|
21
|
+
|
|
22
|
+
import { test } from 'node:test';
|
|
23
|
+
import assert from 'node:assert/strict';
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { PassThrough } from 'node:stream';
|
|
27
|
+
import { createRequire } from 'node:module';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
32
|
+
const { readTokenFromInput } = require(path.join(ROOT, 'scripts', 'bongos', 'paste-token.js'));
|
|
33
|
+
|
|
34
|
+
const sink = () => { const s = new PassThrough(); s.text = ''; s.on('data', (c) => { s.text += c; }); return s; };
|
|
35
|
+
|
|
36
|
+
// ── 4. the token can arrive without touching the command line ────────────────
|
|
37
|
+
|
|
38
|
+
test('a piped token is read from stdin and trimmed', async () => {
|
|
39
|
+
const input = new PassThrough();
|
|
40
|
+
const p = readTokenFromInput({ input, output: sink() });
|
|
41
|
+
input.end(' abcdef0123456789abcdef0123456789 \r\n');
|
|
42
|
+
assert.equal(await p, 'abcdef0123456789abcdef0123456789');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('an interactive prompt reads one line and never echoes the token', async () => {
|
|
46
|
+
const input = new PassThrough();
|
|
47
|
+
input.isTTY = true;
|
|
48
|
+
const modes = [];
|
|
49
|
+
input.setRawMode = (on) => { modes.push(on); input.isRaw = on; };
|
|
50
|
+
const output = sink();
|
|
51
|
+
const p = readTokenFromInput({ input, output });
|
|
52
|
+
input.write('abcdef01234567');
|
|
53
|
+
input.write('X\u007f'); // a typo, then backspace
|
|
54
|
+
input.write('89abcdef\r');
|
|
55
|
+
const token = await p;
|
|
56
|
+
assert.equal(token, 'abcdef0123456789abcdef');
|
|
57
|
+
assert.ok(!output.text.includes('abcdef'), `the token must not be echoed, got: ${JSON.stringify(output.text)}`);
|
|
58
|
+
assert.match(output.text, /token/i, 'it asks for the token');
|
|
59
|
+
assert.deepEqual(modes, [true, false], 'raw mode is switched on for the read and back off after');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Ctrl+C at the prompt cancels instead of saving a half-typed token', async () => {
|
|
63
|
+
const input = new PassThrough();
|
|
64
|
+
input.isTTY = true;
|
|
65
|
+
input.setRawMode = () => {};
|
|
66
|
+
const p = readTokenFromInput({ input, output: sink() });
|
|
67
|
+
input.write('abc\u0003');
|
|
68
|
+
await assert.rejects(p, /cancel/i);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('paste-token falls back to the prompt when no token argument is given', () => {
|
|
72
|
+
const src = fs.readFileSync(path.join(ROOT, 'scripts', 'bongos', 'paste-token.js'), 'utf8');
|
|
73
|
+
const main = src.slice(src.indexOf('async function main()'));
|
|
74
|
+
assert.match(main, /readTokenFromInput\(/, 'main reads the token from input when argv has none');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ── 2 + 3. the hall's one-liner works in a default Windows PowerShell ─────────
|
|
78
|
+
|
|
79
|
+
const routeSrc = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'routes', 'auth.js'), 'utf8');
|
|
80
|
+
const issueBlock = (() => {
|
|
81
|
+
const start = routeSrc.indexOf("'/auth/cli-token/issue'");
|
|
82
|
+
const next = routeSrc.indexOf('\n router.', start);
|
|
83
|
+
return routeSrc.slice(start, next === -1 ? routeSrc.length : next);
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
test('the issue route also composes a Windows one-liner that avoids npx.ps1', () => {
|
|
87
|
+
const m = issueBlock.match(/paste_command_windows:\s*issuer\s*\?\s*`([^`]+)`\s*:\s*`([^`]+)`/);
|
|
88
|
+
assert.ok(m, 'paste_command_windows should be a readable template literal pair');
|
|
89
|
+
for (const cmd of [m[1], m[2]]) {
|
|
90
|
+
assert.match(cmd, /^npx\.cmd bongos paste-token \$\{token\}/, `npx.cmd, not npx — got: ${cmd}`);
|
|
91
|
+
}
|
|
92
|
+
assert.match(m[1], /--api-base \$\{issuer\}/, 'the Windows form carries the issuer too');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const settingsJs = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'settings.js'), 'utf8');
|
|
96
|
+
const issueFn = (settingsJs.match(/async function issueCliToken\([\s\S]*?\n \}\n/) || [''])[0];
|
|
97
|
+
|
|
98
|
+
test('the hall shows the Windows one-liner to a Windows browser', () => {
|
|
99
|
+
assert.ok(issueFn, 'issueCliToken is a whole function');
|
|
100
|
+
assert.match(issueFn, /paste_command_windows/, 'it reads the Windows form');
|
|
101
|
+
assert.match(settingsJs, /function isWindowsBrowser\(/, 'it decides by the browser platform');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('the hall says the command only runs from inside the project folder', () => {
|
|
105
|
+
assert.match(issueFn, /cd/, 'the instruction names cd');
|
|
106
|
+
assert.match(issueFn, /only (runs|works) (from )?(there|inside)/i, 'and says why');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ── grade round 2 (task 1005033) ─────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
test('a pipe that never closes still settles on the first line', async () => {
|
|
112
|
+
const input = new PassThrough();
|
|
113
|
+
const p = readTokenFromInput({ input, output: sink() });
|
|
114
|
+
input.write('abcdef0123456789abcdef\nsomething else that never ends');
|
|
115
|
+
assert.equal(await p, 'abcdef0123456789abcdef');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('Ctrl+D on an empty prompt cancels instead of saving nothing', async () => {
|
|
119
|
+
const input = new PassThrough();
|
|
120
|
+
input.isTTY = true;
|
|
121
|
+
input.setRawMode = () => {};
|
|
122
|
+
const p = readTokenFromInput({ input, output: sink() });
|
|
123
|
+
input.write('\u0004');
|
|
124
|
+
await assert.rejects(p, /cancel/i);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('the hall offers the token-free command with the token on its own button', () => {
|
|
128
|
+
assert.match(issueFn, /bongos paste-token\$\{body\.api_base/, 'a command with no token in it');
|
|
129
|
+
assert.match(issueFn, /Copy token/, 'the token is copied separately');
|
|
130
|
+
assert.match(issueFn, /Keep the token out of your shell history/, 'and it says why');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── 1. the owner hears about Device Flow being off ────────────────────────────
|
|
134
|
+
|
|
135
|
+
const { raiseDeviceFlowOffBlocker, SOURCE, SOURCE_REF } = require(path.join(ROOT, 'src', 'bongos', 'device-flow-blocker.js'));
|
|
136
|
+
|
|
137
|
+
test('a device_flow_disabled refusal files ONE owner blocker through ideas.fileBlocker', async () => {
|
|
138
|
+
const filed = [];
|
|
139
|
+
const resolveOptional = (port) => (port === 'ideas.fileBlocker' ? async (b) => { filed.push(b); return { id: 1 }; } : null);
|
|
140
|
+
const out = await raiseDeviceFlowOffBlocker({ reason: 'device_flow_disabled', origin: 'https://x.example', resolveOptional });
|
|
141
|
+
assert.deepEqual(out, { id: 1 });
|
|
142
|
+
assert.equal(filed.length, 1);
|
|
143
|
+
assert.equal(filed[0].source, SOURCE);
|
|
144
|
+
assert.equal(filed[0].sourceRef, SOURCE_REF, 'a fixed source_ref makes the writer idempotent');
|
|
145
|
+
assert.match(filed[0].title, /Enable Device Flow/);
|
|
146
|
+
assert.match(filed[0].bodyMd, /https:\/\/x\.example\/builders\/settings/);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('any other start failure files nothing', async () => {
|
|
150
|
+
let called = false;
|
|
151
|
+
const resolveOptional = () => async () => { called = true; };
|
|
152
|
+
assert.equal(await raiseDeviceFlowOffBlocker({ reason: 'bad_client_id', resolveOptional }), null);
|
|
153
|
+
assert.equal(called, false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('no ideas module, or a failing write, never breaks the sign-in reply', async () => {
|
|
157
|
+
assert.equal(await raiseDeviceFlowOffBlocker({ reason: 'device_flow_disabled', resolveOptional: () => null }), null);
|
|
158
|
+
const boom = () => async () => { throw new Error('db down'); };
|
|
159
|
+
assert.equal(await raiseDeviceFlowOffBlocker({ reason: 'device_flow_disabled', resolveOptional: boom }), null);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('the device/start route raises it on the refusal', () => {
|
|
163
|
+
const start = routeSrc.slice(routeSrc.indexOf("'/auth/device/start'"), routeSrc.indexOf("'/auth/device/start'") + 2500);
|
|
164
|
+
assert.match(start, /raiseDeviceFlowOffBlocker\(\{ reason/);
|
|
165
|
+
});
|
package/tests/provision.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { strict as assert } from 'node:assert';
|
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
17
18
|
import { join } from 'node:path';
|
|
18
19
|
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
@@ -905,12 +906,15 @@ t('installedCoreTarball: reuses a consumer instance file: core pin; null on a so
|
|
|
905
906
|
});
|
|
906
907
|
|
|
907
908
|
await ta('scaffoldStandaloneRepo: on a consumer control plane it REUSES the pinned tarball — no package-core rebuild (task 1002275)', async () => {
|
|
908
|
-
// the control plane is itself a consumer instance: its package.json file:-pins the core
|
|
909
|
+
// the control plane is itself a consumer instance: its package.json file:-pins the core.
|
|
910
|
+
// Pinned to the REAL running CORE_VERSION — a fixed old literal would now be STALE
|
|
911
|
+
// (task 1002450) and exercise the recovery instead of the reuse this test is named for.
|
|
912
|
+
const coreV = P.coreVersionSafe();
|
|
909
913
|
const cp = mkdtempSync(join(tmpdir(), 'cptgz-cp-'));
|
|
910
914
|
mkdirSync(join(cp, 'vendor'));
|
|
911
|
-
const pinned = join(cp, 'vendor',
|
|
915
|
+
const pinned = join(cp, 'vendor', `bongos-core-${coreV}.tgz`);
|
|
912
916
|
writeFileSync(pinned, 'x');
|
|
913
|
-
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core':
|
|
917
|
+
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': `file:vendor/bongos-core-${coreV}.tgz` } }));
|
|
914
918
|
const base = mkdtempSync(join(tmpdir(), 'cptgz-standalone-')); // FRESH base → the scaffold proceeds
|
|
915
919
|
const origBase = P.CONFIG.standaloneBase;
|
|
916
920
|
P.CONFIG.standaloneBase = base;
|
|
@@ -931,6 +935,127 @@ await ta('scaffoldStandaloneRepo: on a consumer control plane it REUSES the pinn
|
|
|
931
935
|
}
|
|
932
936
|
});
|
|
933
937
|
|
|
938
|
+
// ── the stale-pin guard (task 1002450) ───────────────────────────────────────────────
|
|
939
|
+
// Rung 1 reused a file:-pinned hub's own tarball with no check it was still the core the
|
|
940
|
+
// hub runs — an early instance was born on a 1.18.14 tarball that was NOT the published
|
|
941
|
+
// 1.18.14 (missing the task-1002419 poller fix) and its first ship never left 'confirmed'.
|
|
942
|
+
// A stale pin must be replaced, loudly, and never by the package-core.js rungs (which
|
|
943
|
+
// abort on a consumer hub and leave the new repo empty — task 1002275).
|
|
944
|
+
const sha512 = (txt) => 'sha512-' + createHash('sha512').update(txt).digest('base64');
|
|
945
|
+
function filePinnedHub(label, bytes = 'x', { installed = null, scope = 'bongos' } = {}) {
|
|
946
|
+
const cp = mkdtempSync(join(tmpdir(), 'stalepin-'));
|
|
947
|
+
mkdirSync(join(cp, 'vendor'));
|
|
948
|
+
const file = `${scope}-core-${label}.tgz`;
|
|
949
|
+
writeFileSync(join(cp, 'vendor', file), bytes);
|
|
950
|
+
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { [`@${scope}/core`]: `file:vendor/${file}` } }));
|
|
951
|
+
if (installed) {
|
|
952
|
+
mkdirSync(join(cp, 'node_modules', '@bongos', 'core'), { recursive: true });
|
|
953
|
+
writeFileSync(join(cp, 'node_modules', '@bongos', 'core', 'package.json'), JSON.stringify({ name: '@bongos/core', version: installed }));
|
|
954
|
+
}
|
|
955
|
+
return { cp, pinned: join(cp, 'vendor', file) };
|
|
956
|
+
}
|
|
957
|
+
function resolveWith(cp, coreV, respond = () => ({ ok: true, stdout: '' })) {
|
|
958
|
+
const cmds = [], logs = [];
|
|
959
|
+
let res, err;
|
|
960
|
+
try {
|
|
961
|
+
res = P.resolveVendorableCoreTarball({ instanceRoot: cp, exec: (c, o) => { cmds.push(c); return respond(c, o); }, log: (m) => logs.push(m), coreV });
|
|
962
|
+
} catch (e) { err = e; }
|
|
963
|
+
return { ...res, cmds, logs, err };
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
t('pinnedTarballVersion: reads both scope aliases; null when the name carries no version', () => {
|
|
967
|
+
assert.equal(PR.pinnedTarballVersion('/x/vendor/bongos-core-1.18.14.tgz'), '1.18.14');
|
|
968
|
+
assert.equal(PR.pinnedTarballVersion('cloudbongos-core-2.0.0.tgz'), '2.0.0', 'the @cloudbongos alias the ARTIFACT regex in upgrade.js misses');
|
|
969
|
+
assert.equal(PR.pinnedTarballVersion('vendor/core.tgz'), null);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
t('stale label on a file:-pinned hub → packs the INSTALLED core it runs, loudly, never package-core.js (task 1002450)', () => {
|
|
973
|
+
const { cp, pinned } = filePinnedHub('1.18.14', 'x', { installed: '1.18.17' });
|
|
974
|
+
const r = resolveWith(cp, '1.18.17');
|
|
975
|
+
assert.ok(!r.err, String(r.err));
|
|
976
|
+
assert.ok(r.logs.some((l) => /STALE/.test(l) && /1\.18\.14/.test(l) && /1\.18\.17/.test(l)), `a loud warning naming both versions, got: ${r.logs.join(' | ')}`);
|
|
977
|
+
assert.notEqual(r.tgz, pinned, 'never vendors the stale tarball');
|
|
978
|
+
assert.ok(r.cmds.some((c) => c.startsWith('npm pack ') && c.includes(join(cp, 'node_modules', '@bongos', 'core'))), 'packs node_modules/@bongos/core despite the file: pin');
|
|
979
|
+
assert.equal(r.tgz, join(r.packDest, 'bongos-core-1.18.17.tgz'));
|
|
980
|
+
assert.ok(!r.cmds.some((c) => /package-core\.js/.test(c)), 'the empty-set abort path is never reached');
|
|
981
|
+
assert.equal(PR.runningCoreVersion({ instanceRoot: cp, coreV: '1.18.17' }), '1.18.17', 'the hall runs coreV, not the leftover label');
|
|
982
|
+
rmSync(cp, { recursive: true, force: true });
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
t('stale label and no installed match → the registry\'s coreV; no registry either → refuses loudly, never an empty repo (task 1002450)', () => {
|
|
986
|
+
const { cp } = filePinnedHub('1.18.14');
|
|
987
|
+
const ok = resolveWith(cp, '1.18.17');
|
|
988
|
+
assert.ok(ok.cmds.some((c) => c.startsWith('npm pack @bongos/core@1.18.17 ')), `packs the published coreV, got: ${ok.cmds.join(' | ')}`);
|
|
989
|
+
assert.equal(ok.tgz, join(ok.packDest, 'bongos-core-1.18.17.tgz'));
|
|
990
|
+
const down = resolveWith(cp, '1.18.17', (c) => (c.startsWith('npm pack ') ? { ok: false, stdout: '' } : { ok: true, stdout: '' }));
|
|
991
|
+
assert.ok(down.err && /refusing to give a new project a stale core/.test(down.err.message), 'a scaffold with nothing current to vendor is refused');
|
|
992
|
+
assert.ok(!down.cmds.some((c) => /package-core\.js/.test(c)));
|
|
993
|
+
rmSync(cp, { recursive: true, force: true });
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
t('same label, different bytes (the charter case) → the registry\'s own bytes for that version; matching bytes → reuse (task 1002450)', () => {
|
|
997
|
+
const { cp, pinned } = filePinnedHub('1.18.14', 'local-build');
|
|
998
|
+
const view = (published) => (c) => (c.startsWith('npm view @bongos/core@1.18.14 dist.integrity') ? { ok: true, stdout: published + '\n' } : { ok: true, stdout: '' });
|
|
999
|
+
const bad = resolveWith(cp, '1.18.14', view(sha512('published-build')));
|
|
1000
|
+
assert.ok(bad.logs.some((l) => /STALE/.test(l) && /same version label/.test(l)), `loud, got: ${bad.logs.join(' | ')}`);
|
|
1001
|
+
assert.ok(bad.cmds.some((c) => c.startsWith('npm pack @bongos/core@1.18.14 ')), 'repacks the canonical published artifact');
|
|
1002
|
+
assert.notEqual(bad.tgz, pinned);
|
|
1003
|
+
const good = resolveWith(cp, '1.18.14', view(sha512('local-build')));
|
|
1004
|
+
assert.equal(good.tgz, pinned, 'identical bytes → reuse');
|
|
1005
|
+
assert.equal(good.packDest, null);
|
|
1006
|
+
assert.ok(!good.logs.some((l) => /STALE|WARNING/.test(l)));
|
|
1007
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
t('unverifiable pin (no coreV, no registry answer) → reused, but SAID out loud; the cloudbongos alias is checked too (task 1002450)', () => {
|
|
1011
|
+
const { cp, pinned } = filePinnedHub('1.18.14');
|
|
1012
|
+
const noCore = resolveWith(cp, null);
|
|
1013
|
+
assert.equal(noCore.tgz, pinned, 'fail-open: a resolution gap never blocks a scaffold');
|
|
1014
|
+
assert.ok(noCore.logs.some((l) => /WARNING: cannot verify/.test(l)), 'but it is not silent');
|
|
1015
|
+
const offline = resolveWith(cp, '1.18.14');
|
|
1016
|
+
assert.equal(offline.tgz, pinned);
|
|
1017
|
+
assert.ok(offline.logs.some((l) => /WARNING: cannot verify/.test(l) && /no integrity/.test(l)));
|
|
1018
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1019
|
+
const alt = filePinnedHub('1.0.0', 'x', { scope: 'cloudbongos' });
|
|
1020
|
+
const r = resolveWith(alt.cp, '1.0.1');
|
|
1021
|
+
assert.ok(r.logs.some((l) => /STALE/.test(l)), 'a cloudbongos-core-* pin is not exempt from the label check');
|
|
1022
|
+
assert.notEqual(r.tgz, alt.pinned);
|
|
1023
|
+
rmSync(alt.cp, { recursive: true, force: true });
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
t('a stale pin\'s recovery never puts an unsafe version into a shell command (task 1002450)', () => {
|
|
1027
|
+
const { cp } = filePinnedHub('1.18.14');
|
|
1028
|
+
const r = resolveWith(cp, '1.18.17;touch pwn');
|
|
1029
|
+
assert.ok(!r.cmds.some((c) => /pwn/.test(c)), `no command carries the label, got: ${r.cmds.join(' | ')}`);
|
|
1030
|
+
assert.ok(r.err && /refusing to give a new project a stale core/.test(r.err.message), 'nothing safe to pack → refused');
|
|
1031
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1032
|
+
// the INSTALLED core's version (read off node_modules) is held to the same rule
|
|
1033
|
+
const inst = filePinnedHub('1.18.14', 'x', { installed: '1.18.17;touch pwn' });
|
|
1034
|
+
const r2 = resolveWith(inst.cp, '1.18.17;touch pwn');
|
|
1035
|
+
assert.ok(!r2.cmds.some((c) => /pwn/.test(c)), `no command carries the installed label, got: ${r2.cmds.join(' | ')}`);
|
|
1036
|
+
assert.ok(r2.err, 'refused');
|
|
1037
|
+
rmSync(inst.cp, { recursive: true, force: true });
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
t('a file:-pinned hub BEHIND the release gives the new project the release, not its own pin (hub-vs-upstream drift, tasks 1004556 + 1002450)', () => {
|
|
1041
|
+
const { cp, pinned } = filePinnedHub('1.18.14');
|
|
1042
|
+
const cmds = [];
|
|
1043
|
+
const exec = (c) => { cmds.push(c); return { ok: true, stdout: /dist-tags\.latest/.test(c) ? '1.18.17\n' : '' }; };
|
|
1044
|
+
const target = PR.projectCoreTarget({ instanceRoot: cp, coreV: '1.18.14', exec, log: () => {} });
|
|
1045
|
+
assert.deepEqual([target.version, target.source], ['1.18.17', 'released']);
|
|
1046
|
+
const r = P.resolveVendorableCoreTarball({ instanceRoot: cp, exec, log: () => {}, coreV: '1.18.14', target });
|
|
1047
|
+
assert.notEqual(r.tgz, pinned, 'the hub\'s own (older) pin is not vendored');
|
|
1048
|
+
assert.ok(cmds.some((c) => c.startsWith('npm pack @bongos/core@1.18.17 ')), 'the release is packed from the registry');
|
|
1049
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
t('publishing one version twice as different bytes is refused loudly by the publish workflow (the publish half of the build-identity guard, task 1002450)', () => {
|
|
1053
|
+
const wf = readFileSync(new URL('../.github/workflows/publish.yml', import.meta.url), 'utf8');
|
|
1054
|
+
assert.match(wf, /npm publish "\$TGZ"/, 'the workflow publishes to the npm registry, where a version is immutable');
|
|
1055
|
+
assert.match(wf, /cannot publish over\|previously published\|EPUBLISHCONFLICT/, 'it recognises the registry\'s refusal');
|
|
1056
|
+
assert.match(wf, /::error title=version already on the registry/, 'and fails the run loudly on it');
|
|
1057
|
+
});
|
|
1058
|
+
|
|
934
1059
|
// ── registry-pinned consumer control plane (task 1002706) ─────────────────────────
|
|
935
1060
|
// After the mothership's registry cutover (task 1002621) its package.json pins
|
|
936
1061
|
// "@bongos/core": "1.19.x" — NO file: pin — so installedCoreTarball returns null and,
|
|
@@ -1001,10 +1126,10 @@ t('resolveVendorableCoreTarball: the ladder picks file: pin → registry pack
|
|
|
1001
1126
|
// One helper per rung so the ONLY thing varying between cases is which shape the
|
|
1002
1127
|
// control-plane root presents. Asserting on the recorded commands is what proves the
|
|
1003
1128
|
// ORDER: a rung that fires early leaves the later rung's command unrun.
|
|
1004
|
-
const run = (instanceRoot, repoRoot) => {
|
|
1129
|
+
const run = (instanceRoot, repoRoot, coreV = '9.9.9') => {
|
|
1005
1130
|
const cmds = [];
|
|
1006
1131
|
const res = P.resolveVendorableCoreTarball({
|
|
1007
|
-
instanceRoot, repoRoot, exec: (c) => { cmds.push(c); }, log: () => {}, coreV
|
|
1132
|
+
instanceRoot, repoRoot, exec: (c) => { cmds.push(c); }, log: () => {}, coreV,
|
|
1008
1133
|
});
|
|
1009
1134
|
return { cmds, ...res };
|
|
1010
1135
|
};
|
|
@@ -1017,9 +1142,9 @@ t('resolveVendorableCoreTarball: the ladder picks file: pin → registry pack
|
|
|
1017
1142
|
mkdirSync(join(filePinned, 'vendor'));
|
|
1018
1143
|
writeFileSync(join(filePinned, 'vendor', 'bongos-core-1.2.3.tgz'), 'x');
|
|
1019
1144
|
writeFileSync(join(filePinned, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': 'file:vendor/bongos-core-1.2.3.tgz' } }));
|
|
1020
|
-
const r1 = run(filePinned, bareRoot);
|
|
1145
|
+
const r1 = run(filePinned, bareRoot, '1.2.3');
|
|
1021
1146
|
assert.equal(r1.tgz, join(filePinned, 'vendor', 'bongos-core-1.2.3.tgz'));
|
|
1022
|
-
assert.
|
|
1147
|
+
assert.ok(r1.cmds.every((c) => c.startsWith('npm view ')), 'rung 1 builds and packs nothing — it reuses bytes already on disk (only the read-only integrity check runs)');
|
|
1023
1148
|
|
|
1024
1149
|
// Rung 2 — a registry-installed core packs, still without touching package-core.js.
|
|
1025
1150
|
const reg = mkdtempSync(join(tmpdir(), 'rvct-reg-'));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// tests/vendored_work_root.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1005100 - feedback instance vendors: in a project that installs the core from npm
|
|
4
|
+
// (toast, core 1.21.115), docs-entropy.js and ship-check.js took their root from
|
|
5
|
+
// path.resolve(__dirname, '..', '..') — the PACKAGE dir under node_modules/@bongos/core.
|
|
6
|
+
// docs-entropy then ran `git ls-files '*.md'` there, enumerated 0 files and died with a
|
|
7
|
+
// "scan defect"; ship-check ran every generator from the package dir, so gen-api-docs read
|
|
8
|
+
// STALE while `npx bongos exec scripts/bongos/gen-api-docs.js --check` from the project
|
|
9
|
+
// said up to date. resolveWorkRoot() is the root these tools work on: the core checkout when
|
|
10
|
+
// the core is a checkout, the project (pointer, else the git top of cwd, else cwd) when it
|
|
11
|
+
// is installed. ship-check spawns the script from the core and runs it in that root.
|
|
12
|
+
//
|
|
13
|
+
// Run: node tests/vendored_work_root.mjs
|
|
14
|
+
|
|
15
|
+
import assert from 'node:assert/strict';
|
|
16
|
+
import { test } from 'node:test';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
|
+
const { resolveWorkRoot, CORE_ROOT } = require('../scripts/bongos/fitness-lib.js');
|
|
27
|
+
const { runStep } = require('../scripts/bongos/ship-check.js');
|
|
28
|
+
|
|
29
|
+
const real = (p) => fs.realpathSync.native(p);
|
|
30
|
+
const tmp = (name) => fs.mkdtempSync(path.join(os.tmpdir(), `vwr-${name}-`));
|
|
31
|
+
|
|
32
|
+
test('a core checkout works on the core root, whatever the cwd', () => {
|
|
33
|
+
assert.equal(resolveWorkRoot({ installed: false, cwd: os.tmpdir() }), CORE_ROOT);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('an installed core with a pointer works on the pointed-at project', () => {
|
|
37
|
+
assert.equal(resolveWorkRoot({ installed: true, cwd: os.tmpdir(), explicit: () => '/proj' }), '/proj');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('an installed core with no pointer works on the git top of cwd, not the package dir', () => {
|
|
41
|
+
const repo = tmp('repo');
|
|
42
|
+
execFileSync('git', ['init', '-q'], { cwd: repo });
|
|
43
|
+
const sub = path.join(repo, 'docs');
|
|
44
|
+
fs.mkdirSync(sub);
|
|
45
|
+
const got = resolveWorkRoot({ installed: true, cwd: sub, explicit: () => null });
|
|
46
|
+
assert.equal(real(got), real(repo));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('an installed core outside any repo falls back to cwd', () => {
|
|
50
|
+
const dir = tmp('norepo');
|
|
51
|
+
const got = resolveWorkRoot({ installed: true, cwd: dir, explicit: () => null, gitTop: () => null });
|
|
52
|
+
assert.equal(got, dir);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('ship-check runs a step from the core but inside the work root', () => {
|
|
56
|
+
const core = tmp('core');
|
|
57
|
+
const inst = tmp('inst');
|
|
58
|
+
fs.mkdirSync(path.join(core, 'scripts', 'bongos'), { recursive: true });
|
|
59
|
+
fs.writeFileSync(path.join(core, 'scripts', 'bongos', 'where.js'), 'console.log("cwd=" + process.cwd());\n');
|
|
60
|
+
const r = runStep({ key: 'w', label: 'w', argv: ['scripts/bongos/where.js'], fix: 'f' }, { coreRoot: core, cwd: inst });
|
|
61
|
+
assert.equal(r.status, 'ok', r.tail);
|
|
62
|
+
assert.match(r.tail, /cwd=/);
|
|
63
|
+
assert.equal(real(r.tail.trim().replace(/^cwd=/, '')), real(inst));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('docs-entropy and ship-check no longer hardwire their root to the package dir', () => {
|
|
67
|
+
for (const f of ['scripts/bongos/docs-entropy.js', 'scripts/bongos/ship-check.js']) {
|
|
68
|
+
const src = fs.readFileSync(path.join(ROOT, f), 'utf8');
|
|
69
|
+
assert.doesNotMatch(src, /const REPO_ROOT = path\.resolve\(__dirname, '\.\.', '\.\.'\)/, f);
|
|
70
|
+
assert.match(src, /resolveWorkRoot\(\)/, f);
|
|
71
|
+
}
|
|
72
|
+
});
|