@bongos/core 1.19.948 → 1.19.949

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.
@@ -1,299 +0,0 @@
1
- // tests/cli_package.mjs — the public @cloudbongos/cli package is client-only AND actually runs.
2
- //
3
- // Guards task 1003679. Two halves, and the second is the one that matters:
4
- //
5
- // 1. Shape: the generated tree carries no server surface, and the redaction gate really fails
6
- // a build when instance identity or a credential shape appears in it.
7
- //
8
- // 2. BEHAVIOUR: pack the tarball, install it into an empty directory with NO repo present, and
9
- // run every advertised verb. This is the only instrument that can prove the file list is
10
- // complete. A static require-closure cannot: src/module-api.js is the module doorway
11
- // (ADR 0083) and by design *names* every kernel capability, so a static walk sees the whole
12
- // server even though the getters are lazy and never resolve. If a real code path needs a
13
- // file the manifest omits, the verb exits with MODULE_NOT_FOUND and this test says so.
14
-
15
- import test from 'node:test';
16
- import assert from 'node:assert/strict';
17
- import fs from 'node:fs';
18
- import os from 'node:os';
19
- import path from 'node:path';
20
- import { spawnSync } from 'node:child_process';
21
- import { fileURLToPath, pathToFileURL } from 'node:url';
22
- import { childEnv } from './helpers.mjs';
23
-
24
- const REPO_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
25
-
26
- // HOME ALONE IS NOT ISOLATION ON WINDOWS (task 1003763).
27
- //
28
- // os.homedir() reads USERPROFILE on win32 and never consults HOME, so
29
- // `env: { ...process.env, HOME: home }` isolated nothing there: the installed CLI
30
- // saw the DEVELOPER'S REAL session file. That is precisely the false green the
31
- // comment below guards against — an unauthenticated-path assertion passing because
32
- // the machine happens to be signed in.
33
- //
34
- // HOMEDRIVE/HOMEPATH are os.homedir()'s second win32 source, so all four go. Named
35
- // rather than inlined twice: this is the third site in the repo to need it (after
36
- // tests/cli_sessions.mjs in task 1003760 and tests/cli_surface.mjs in task 1003941),
37
- // and every one of them got it half-right first.
38
- //
39
- // AND AN INHERITED API BASE IS NOT ISOLATION EITHER (task 1004049). The same
40
- // shape of hole, one env var over: resolveApiBase() reads the CANONICAL
41
- // <PREFIX>_API_BASE before any legacy spelling, so the `CLOUDBONGOS_API_BASE:
42
- // 'http://127.0.0.1:45999'` the verbs below rely on to be a DEAD port is silently
43
- // shadowed on any box that exports the canonical one — every Bongos session box.
44
- // The verbs then made real calls to the live instance instead of failing fast
45
- // against a closed port, which is why this test ran for minutes and timed out
46
- // rather than failing with something readable. childEnv strips every spelling.
47
- function homelessEnv(home, extra = {}) {
48
- return childEnv({ HOME: home, USERPROFILE: home, HOMEDRIVE: '', HOMEPATH: home, ...extra });
49
- }
50
- const BUILDER = path.join(REPO_ROOT, 'scripts', 'gds', 'build-cli-package.js');
51
- // Running npm from a test (task 1003529). On Windows `npm` is a .cmd shim: bare
52
- // 'npm' is ENOENT and 'npm.cmd' is EINVAL under Node 24's CVE-2024-27980 mitigation,
53
- // both surfacing as an inscrutable `null !== 0`. `shell: true` clears it but
54
- // concatenates args unescaped (DEP0190), which breaks on any tmp path with a space.
55
- // So invoke npm's own entrypoint through node: no shell, args stay arrays.
56
- const NPM_CLI = [
57
- path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js'),
58
- path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
59
- ].find((p) => fs.existsSync(p));
60
- const npm = (args, opts) => (NPM_CLI
61
- ? spawnSync(process.execPath, [NPM_CLI, ...args], opts)
62
- : spawnSync('npm', args, opts));
63
-
64
- // import() takes a URL, not a filesystem path: on win32 a bare 'C:\…' is read as
65
- // the 'c:' protocol and throws ERR_UNSUPPORTED_ESM_URL_SCHEME before any test runs
66
- // (task 1003529). pathToFileURL is the portable spelling.
67
- const mod = await import(pathToFileURL(BUILDER).href);
68
- const { build, FILES, VERBS, CHECKOUT_ONLY, ALLOWED_SRC_BONGOS, PACKAGE_NAME } = mod.default ?? mod;
69
-
70
- function tmpdir(tag) {
71
- return fs.mkdtempSync(path.join(os.tmpdir(), `cli-pkg-${tag}-`));
72
- }
73
-
74
- // ── 1. Shape ────────────────────────────────────────────────────────────────────────────────
75
-
76
- test('the generated package contains no server surface', () => {
77
- const out = tmpdir('shape');
78
- const { files } = build({ out, quiet: true });
79
-
80
- const kernel = files.filter((f) => f.startsWith('src/bongos/'));
81
- assert.deepEqual(kernel, [...ALLOWED_SRC_BONGOS],
82
- `only ${[...ALLOWED_SRC_BONGOS].join(', ')} may ship from the kernel, got ${kernel.join(', ')}`);
83
-
84
- for (const f of files) {
85
- assert.ok(!f.startsWith('src/bongos/routes/'), `${f}: routes must never ship`);
86
- assert.ok(!/(^|\/)(pool|db|server|serve-internal)\.js$/.test(f), `${f}: server surface must never ship`);
87
- assert.ok(!f.startsWith('migrations/'), `${f}: migrations must never ship`);
88
- assert.ok(!f.startsWith('infra/'), `${f}: infra must never ship`);
89
- }
90
- fs.rmSync(out, { recursive: true, force: true });
91
- });
92
-
93
- test('the one kernel file that ships imports nothing', () => {
94
- const src = fs.readFileSync(path.join(REPO_ROOT, 'src', 'bongos', 'api-prefix.js'), 'utf8');
95
- const rel = [...src.matchAll(/require\(\s*['"](\.[^'"]+)['"]\s*\)/g)].map((m) => m[1]);
96
- assert.deepEqual(rel, [], `api-prefix.js must stay dependency-free, found: ${rel.join(', ')}`);
97
- });
98
-
99
- test('every file in the manifest exists', () => {
100
- for (const rel of FILES) {
101
- assert.ok(fs.existsSync(path.join(REPO_ROOT, rel)), `manifest names a missing file: ${rel}`);
102
- }
103
- });
104
-
105
- test('every advertised verb maps to a script the manifest ships', () => {
106
- for (const [verb, def] of Object.entries(VERBS)) {
107
- assert.ok(FILES.includes(`scripts/gds/${def.script}`),
108
- `verb "${verb}" runs scripts/gds/${def.script}, which the manifest does not ship`);
109
- }
110
- });
111
-
112
- test('the redaction gate fails the build on an injected needle and on a credential shape', () => {
113
- const out = tmpdir('gate');
114
-
115
- // A domain that really is in the tree, supplied as forbidden → must refuse to emit.
116
- const withNeedle = spawnSync(process.execPath, [BUILDER, '--out', out, '--quiet'], {
117
- cwd: REPO_ROOT, encoding: 'utf8',
118
- env: { ...process.env, CLI_PACKAGE_FORBIDDEN: 'cli-lib' },
119
- });
120
- assert.notEqual(withNeedle.status, 0, 'an instance needle present in the tree must fail the build');
121
- assert.match(withNeedle.stderr, /redaction gate FAILED/);
122
-
123
- // A planted credential shape must fail too, with no needle configured at all.
124
- const planted = path.join(REPO_ROOT, 'scripts', 'gds', '.cli-pkg-gate-probe.js');
125
- const victim = path.join(REPO_ROOT, 'scripts', 'gds', 'preflight.js');
126
- const original = fs.readFileSync(victim, 'utf8');
127
- try {
128
- fs.writeFileSync(victim, `${original}\n// ghp_0123456789abcdefghijABCDEFGHIJ0123\n`);
129
- const withToken = spawnSync(process.execPath, [BUILDER, '--out', out, '--quiet'], {
130
- cwd: REPO_ROOT, encoding: 'utf8',
131
- });
132
- assert.notEqual(withToken.status, 0, 'a github token shape must fail the build');
133
- assert.match(withToken.stderr, /github token/);
134
- } finally {
135
- fs.writeFileSync(victim, original);
136
- fs.rmSync(planted, { force: true });
137
- }
138
- fs.rmSync(out, { recursive: true, force: true });
139
- });
140
-
141
- test('a clean build emits and reports itself', () => {
142
- const out = tmpdir('clean');
143
- const res = spawnSync(process.execPath, [BUILDER, '--out', out], { cwd: REPO_ROOT, encoding: 'utf8' });
144
- assert.equal(res.status, 0, `clean build failed:\n${res.stderr}`);
145
- assert.match(res.stdout, /redaction gate: clean/);
146
- assert.ok(fs.existsSync(path.join(out, 'package.json')));
147
- assert.ok(fs.existsSync(path.join(out, 'bin', 'bongos.js')));
148
-
149
- const pkg = JSON.parse(fs.readFileSync(path.join(out, 'package.json'), 'utf8'));
150
- assert.equal(pkg.name, PACKAGE_NAME);
151
- assert.deepEqual(Object.keys(pkg.dependencies), ['undici'], 'exactly one runtime dependency');
152
- assert.equal(pkg.bin.bongos, 'bin/bongos.js');
153
- assert.equal(pkg.publishConfig.access, 'public');
154
- assert.ok(!('repository' in pkg), 'no repository URL while the core repo is private');
155
- fs.rmSync(out, { recursive: true, force: true });
156
- });
157
-
158
- // ── 2. Behaviour: install the tarball where there is no repo, and run it ─────────────────────
159
-
160
- test('the packed tarball installs and every verb runs with no repo present', (t) => {
161
- const buildOut = tmpdir('pack');
162
- const home = tmpdir('home');
163
- const site = tmpdir('site');
164
-
165
- const built = spawnSync(process.execPath, [BUILDER, '--out', buildOut, '--quiet'], {
166
- cwd: REPO_ROOT, encoding: 'utf8',
167
- });
168
- assert.equal(built.status, 0, `build failed:\n${built.stderr}`);
169
-
170
- const packed = npm(['pack', '--silent', '--pack-destination', site], {
171
- cwd: buildOut, encoding: 'utf8',
172
- });
173
- assert.equal(packed.status, 0, `npm pack failed:\n${packed.stderr}`);
174
- const tarball = fs.readdirSync(site).find((f) => f.endsWith('.tgz'));
175
- assert.ok(tarball, `no tarball produced in ${site}`);
176
-
177
- // An empty directory: no repo, no core, no config, and a HOME of its own so a real session
178
- // file on this machine cannot make a verb look healthier than it is.
179
- fs.writeFileSync(path.join(site, 'package.json'), JSON.stringify({ name: 'probe', private: true }));
180
- const installed = npm(['install', '--no-audit', '--no-fund', '--silent', path.join(site, tarball)], {
181
- cwd: site, encoding: 'utf8', env: homelessEnv(home),
182
- });
183
- assert.equal(installed.status, 0, `npm install failed:\n${installed.stderr}`);
184
-
185
- // The installed shim proves npm wired `bin` up; the verbs then run through the
186
- // real entrypoint rather than the shim, for the same Node-24-on-Windows reason as
187
- // NPM_CLI above — spawning .bin/bongos.cmd is EINVAL (task 1003529).
188
- const shim = path.join(site, 'node_modules', '.bin', process.platform === 'win32' ? 'bongos.cmd' : 'bongos');
189
- assert.ok(fs.existsSync(shim), 'the package did not install a `bongos` binary');
190
- const bongos = path.join(site, 'node_modules', PACKAGE_NAME, 'bin', 'bongos.js');
191
- assert.ok(fs.existsSync(bongos), `the installed package has no bin/bongos.js at ${bongos}`);
192
-
193
- const runVerb = (args) => spawnSync(process.execPath, [bongos, ...args], {
194
- cwd: site, encoding: 'utf8', input: '',
195
- env: homelessEnv(home, { CLOUDBONGOS_API_BASE: 'http://127.0.0.1:45999', NO_COLOR: '1' }),
196
- });
197
-
198
- // help and version must work with nothing configured at all.
199
- const help = runVerb(['help']);
200
- assert.equal(help.status, 0, `bongos help failed:\n${help.stderr}`);
201
- assert.match(help.stdout, /bongos login https:\/\//, 'help must lead with how to sign in');
202
-
203
- const version = runVerb(['version']);
204
- assert.equal(version.status, 0);
205
- assert.match(version.stdout, /^bongos \d+\.\d+\.\d+/m);
206
-
207
- // Every advertised verb must actually LOAD its script. Args are chosen so the dispatcher
208
- // cannot answer on the script's behalf — `--help` on a non-selfHelp verb is intercepted by
209
- // the dispatcher and would leave the script untouched, which is how this test could pass
210
- // while proving nothing. Each verb below reaches its own code, then fails on the dead API
211
- // base or on missing arguments. Failing that way is fine; failing to find a file is not.
212
- const LOAD_ARGS = {
213
- login: ['login'],
214
- // `--no-open` is load-bearing, not cosmetic (task 1003909). HOME is a throwaway here, so
215
- // there is no session and reauth takes its no-session path — which launches the builder's
216
- // default browser at the dead API base (`rundll32 url.dll,FileProtocolHandler` on Windows).
217
- // Without the flag, every run of this suite pops a connection-refused tab on the machine
218
- // under test. The flag skips only the spawn: the script still loads, still prints its
219
- // guidance, still exits 10, so every assertion below is unchanged.
220
- reauth: ['reauth', '--no-open'],
221
- setup: ['setup'],
222
- start: ['start'],
223
- status: ['status'],
224
- task: ['task', 'show', '1'],
225
- recall: ['recall', 'probe'],
226
- cost: ['cost'],
227
- release: ['release'],
228
- api: ['api', 'GET', '/api/gds/me'],
229
- };
230
- assert.deepEqual(
231
- Object.keys(LOAD_ARGS).sort(), Object.keys(VERBS).sort(),
232
- 'every advertised verb needs an entry here, or it goes unexercised',
233
- );
234
-
235
- // No verb may launch a GUI on the machine running the suite (task 1003909). A script that can
236
- // spawn a browser has two ways to be safe here, and it must pick one:
237
- // * it offers an opt-out flag → the LOAD_ARGS entry above passes it, or
238
- // * it stops short of the launch → STOPS_SHORT_OF_LAUNCH names a marker that must stay absent.
239
- // Proving it, rather than assuming it, is the point: a verb that starts reaching its launcher
240
- // fails here instead of on someone's desktop.
241
- const LAUNCHES_BROWSER = /FileProtocolHandler|xdg-open/;
242
- const OPT_OUT = '--no-open';
243
- const STOPS_SHORT_OF_LAUNCH = {
244
- // `bongos login` opens a browser from inside the device flow, several network steps past the
245
- // instance-URL prompt — and with empty stdin that prompt is never answered, so the process
246
- // ends right there. `▸ Connecting to` is the first thing printed once a URL is accepted, so
247
- // its ABSENCE is the proof this run stopped well short of the launch.
248
- login: /Connecting to/,
249
- };
250
- const mustStopShort = {};
251
- for (const [verb, args] of Object.entries(LOAD_ARGS)) {
252
- const src = fs.readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', VERBS[verb].script), 'utf8');
253
- if (!LAUNCHES_BROWSER.test(src)) continue;
254
- if (src.includes(OPT_OUT)) {
255
- assert.ok(args.includes(OPT_OUT),
256
- `bongos ${verb} can spawn a browser and accepts ${OPT_OUT} — pass it in LOAD_ARGS, or this suite opens a window on the builder's machine`);
257
- continue;
258
- }
259
- assert.ok(STOPS_SHORT_OF_LAUNCH[verb],
260
- `bongos ${verb} can spawn a browser and has no ${OPT_OUT} — give it one, or add a STOPS_SHORT_OF_LAUNCH entry proving this run never reaches the launch`);
261
- mustStopShort[verb] = STOPS_SHORT_OF_LAUNCH[verb];
262
- }
263
-
264
- for (const [verb, args] of Object.entries(LOAD_ARGS)) {
265
- const res = runVerb(args);
266
- const all = `${res.stdout}\n${res.stderr}`;
267
- if (mustStopShort[verb]) {
268
- assert.doesNotMatch(all, mustStopShort[verb],
269
- `bongos ${verb} got past the point where it can spawn a browser on this machine:\n${all}`);
270
- }
271
- assert.ok(!/Cannot find module|MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND/.test(all),
272
- `bongos ${verb} hit a missing file — the manifest is incomplete:\n${all}`);
273
- assert.ok(!/ERR_REQUIRE_ESM|SyntaxError|ReferenceError/.test(all),
274
- `bongos ${verb} failed to load:\n${all}`);
275
- // `TypeError: fetch failed` is what an unreachable API base looks like and is expected
276
- // here; any OTHER TypeError means the package itself is broken.
277
- assert.ok(!/TypeError(?!: fetch failed)/.test(all),
278
- `bongos ${verb} threw a real TypeError:\n${all}`);
279
- assert.notEqual(res.status, null, `bongos ${verb} was killed by a signal:\n${all}`);
280
- // The dispatcher's own help card is proof the script was never reached.
281
- assert.ok(!/^bongos \w+ — .*\nForwards to scripts/m.test(all),
282
- `bongos ${verb} was answered by the dispatcher instead of loading:\n${all}`);
283
- }
284
-
285
- // The excluded verbs must teach the next step, not print a bare "unknown command".
286
- for (const verb of Object.keys(CHECKOUT_ONLY)) {
287
- const res = runVerb([verb]);
288
- const all = `${res.stdout}\n${res.stderr}`;
289
- assert.match(all, /is not in the public CLI/, `bongos ${verb} must say why it is absent`);
290
- assert.match(all, /checkout of the project repo/, `bongos ${verb} must point at a way to get a checkout`);
291
- assert.ok(!/unknown command/.test(all), `bongos ${verb} must not read as a typo`);
292
- }
293
-
294
- // An unknown verb still behaves like one.
295
- const bogus = runVerb(['definitely-not-a-verb']);
296
- assert.match(bogus.stderr, /unknown command/);
297
-
298
- for (const d of [buildOut, home, site]) fs.rmSync(d, { recursive: true, force: true });
299
- });