@bongos/core 1.19.834 → 1.19.835
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 +13 -13
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/provision-repo.js +53 -14
- package/scripts/gds/provision-teardown.js +1 -1
- package/scripts/gds/provision.js +3 -1
- package/src/module-api.js +1 -1
- package/tests/provision.mjs +89 -1
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.835",
|
|
6
|
+
"core_contract": "1.19.835",
|
|
7
|
+
"source_commit": "31b17fb901dd65b7c6893f797e935c3b503a0de9",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-17T16:
|
|
9
|
+
"built_at": "2026-09-17T16:17:34.798Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 493,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2767,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "a2ec29793c06b863434f540138312423bddbe46023b7584468bbbdb7d5dfd2b2",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2562,7 +2562,7 @@
|
|
|
2562
2562
|
{
|
|
2563
2563
|
"path": "docs/module-api-changelog.md",
|
|
2564
2564
|
"mode": "0000644",
|
|
2565
|
-
"sha256": "
|
|
2565
|
+
"sha256": "6cd1402c15f4a97fe2e7da159ad42f283992d01058781df987f5948c25ff9df4"
|
|
2566
2566
|
},
|
|
2567
2567
|
{
|
|
2568
2568
|
"path": "docs/modules-contract.md",
|
|
@@ -7852,12 +7852,12 @@
|
|
|
7852
7852
|
{
|
|
7853
7853
|
"path": "package-lock.json",
|
|
7854
7854
|
"mode": "0000644",
|
|
7855
|
-
"sha256": "
|
|
7855
|
+
"sha256": "72ff1b680bbebe422781c8e96ff9da2627b98581a871e0f826907525a0eecf04"
|
|
7856
7856
|
},
|
|
7857
7857
|
{
|
|
7858
7858
|
"path": "package.json",
|
|
7859
7859
|
"mode": "0000644",
|
|
7860
|
-
"sha256": "
|
|
7860
|
+
"sha256": "95c3f6f48a670b840fbf7b1f05f97b5fa93fa06ed08b6322d766b19e46beeef5"
|
|
7861
7861
|
},
|
|
7862
7862
|
{
|
|
7863
7863
|
"path": "public-docs/index.html",
|
|
@@ -8772,12 +8772,12 @@
|
|
|
8772
8772
|
{
|
|
8773
8773
|
"path": "scripts/gds/provision-repo.js",
|
|
8774
8774
|
"mode": "0000644",
|
|
8775
|
-
"sha256": "
|
|
8775
|
+
"sha256": "c8d4ce3004cafbd1d70ee72845700c95c85da41ced29fe059af333869a75cc03"
|
|
8776
8776
|
},
|
|
8777
8777
|
{
|
|
8778
8778
|
"path": "scripts/gds/provision-teardown.js",
|
|
8779
8779
|
"mode": "0000644",
|
|
8780
|
-
"sha256": "
|
|
8780
|
+
"sha256": "df4187852cf0f75a12571607af95782d16f1655aa4314152078cb9ee9b3d0f7c"
|
|
8781
8781
|
},
|
|
8782
8782
|
{
|
|
8783
8783
|
"path": "scripts/gds/provision-units.js",
|
|
@@ -8787,7 +8787,7 @@
|
|
|
8787
8787
|
{
|
|
8788
8788
|
"path": "scripts/gds/provision.js",
|
|
8789
8789
|
"mode": "0000644",
|
|
8790
|
-
"sha256": "
|
|
8790
|
+
"sha256": "4f60207affe476f637b2b2c13c38ab785940deef9bf8b5a8fa43b5d6448a408a"
|
|
8791
8791
|
},
|
|
8792
8792
|
{
|
|
8793
8793
|
"path": "scripts/gds/publish-credential-check.js",
|
|
@@ -9777,7 +9777,7 @@
|
|
|
9777
9777
|
{
|
|
9778
9778
|
"path": "src/module-api.js",
|
|
9779
9779
|
"mode": "0000644",
|
|
9780
|
-
"sha256": "
|
|
9780
|
+
"sha256": "a5a74874643eb65e4e9727c492b8990e42dd5b28d13c40e5a523abcbc1aa320d"
|
|
9781
9781
|
},
|
|
9782
9782
|
{
|
|
9783
9783
|
"path": "src/module-loader/catalog.js",
|
|
@@ -12592,7 +12592,7 @@
|
|
|
12592
12592
|
{
|
|
12593
12593
|
"path": "tests/provision.mjs",
|
|
12594
12594
|
"mode": "0000644",
|
|
12595
|
-
"sha256": "
|
|
12595
|
+
"sha256": "56be00457be12f34e12658e032285865a6e3a2997df629e4d8dec3ec78c52b31"
|
|
12596
12596
|
},
|
|
12597
12597
|
{
|
|
12598
12598
|
"path": "tests/provision_disconnect.mjs",
|
|
@@ -2142,5 +2142,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2142
2142
|
landed since 1.19.832 with no explicit bump. run 35243115057. (task 1002620)
|
|
2143
2143
|
1.19.834 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2144
2144
|
landed since 1.19.833 with no explicit bump. run 35244719662. (task 1002620)
|
|
2145
|
+
1.19.835 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2146
|
+
landed since 1.19.834 with no explicit bump. run 35245617627. (task 1002620)
|
|
2145
2147
|
---------------------------------------------------------------------------
|
|
2146
2148
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.835",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.835",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.835",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -512,6 +512,8 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
512
512
|
throw new Error(`standalone instance ${inst.slug}: target_ref "${inst.target_ref || '(unset)'}" is not an owner/repo — set it (e.g. <owner>/${inst.slug}) so the scaffold knows which repo to clone + push.`);
|
|
513
513
|
}
|
|
514
514
|
if (alreadyScaffolded(root, mode)) {
|
|
515
|
+
const resumed = deps.apply ? await resumeUnpushedScaffold(inst, deps, { root, repo, mode }) : null;
|
|
516
|
+
if (resumed) return resumed;
|
|
515
517
|
log(` [scaffold] ${root} already scaffolded (mode=${mode}) — skipping`);
|
|
516
518
|
return { already: true };
|
|
517
519
|
}
|
|
@@ -541,13 +543,8 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
541
543
|
// command or makeExec's env (makeExec surfaces both), and is removed in a `finally` so a
|
|
542
544
|
// failed git op can't leave it on disk. No token (org-owned repo, or secret-box off) →
|
|
543
545
|
// fall back to the SSH `origin` remote.
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
// carries only the `x-access-token` username (no secret), so it is safe in a logged argv.
|
|
547
|
-
const credFile = ownerToken ? path.join(os.tmpdir(), `bongos-gitcred-${inst.slug}-${process.pid}`) : null;
|
|
548
|
-
const httpsUrl = `https://x-access-token@github.com/${repo.owner}/${repo.name}.git`;
|
|
549
|
-
const credOpt = credFile ? `-c credential.helper='store --file=${credFile}' ` : '';
|
|
550
|
-
if (credFile) writeFile(credFile, `https://x-access-token:${ownerToken}@github.com\n`, { mode: 0o600 });
|
|
546
|
+
const cred = await scaffoldCredential(inst, repo, deps, writeFile);
|
|
547
|
+
const { credFile, httpsUrl, credOpt } = cred;
|
|
551
548
|
|
|
552
549
|
try {
|
|
553
550
|
// 1. Materialize the working tree.
|
|
@@ -680,13 +677,7 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
680
677
|
// destroying their work. Adopt's commit is a descendant of the tip it cloned, so an
|
|
681
678
|
// ordinary push fast-forwards; if the remote moved underneath us the rejection is the
|
|
682
679
|
// correct outcome and a re-run picks up the new tip.
|
|
683
|
-
|
|
684
|
-
exec(`git -C ${root} ${credOpt}push ${httpsUrl} HEAD:main`);
|
|
685
|
-
log(` [scaffold] ${mode === 'adopt' ? 'pushed the adopt layer' : 'pushed'} to ${repo.owner}/${repo.name}@main via the owner's GitHub token (task 1002317)`);
|
|
686
|
-
} else {
|
|
687
|
-
exec(`git -C ${root} push -u origin main`);
|
|
688
|
-
log(` [scaffold] pushed ${mode === 'adopt' ? 'the adopt layer' : 'initial scaffold'} to ${repo.owner}/${repo.name}@main (SSH origin)`);
|
|
689
|
-
}
|
|
680
|
+
pushScaffold({ exec, log, root, repo, mode, cred });
|
|
690
681
|
return { ok: true, scaffolded: true, mode };
|
|
691
682
|
} finally {
|
|
692
683
|
// Always remove the credential file — a failed clone/push must not leave it on disk.
|
|
@@ -694,6 +685,54 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
694
685
|
}
|
|
695
686
|
}
|
|
696
687
|
|
|
688
|
+
// The owner-token credential the scaffold's git network ops ride (rationale at the call site in
|
|
689
|
+
// scaffoldStandaloneRepo). The URL carries only the `x-access-token` username, so it is safe in a
|
|
690
|
+
// logged argv. credFile null → no token → SSH origin fallback; the caller `rm -f`s it in a finally.
|
|
691
|
+
async function scaffoldCredential(inst, repo, deps, writeFile) {
|
|
692
|
+
const ownerToken = await resolveOwnerGithubToken(inst, deps, { tag: '[scaffold]' });
|
|
693
|
+
const credFile = ownerToken ? path.join(os.tmpdir(), `bongos-gitcred-${inst.slug}-${process.pid}`) : null;
|
|
694
|
+
const httpsUrl = `https://x-access-token@github.com/${repo.owner}/${repo.name}.git`;
|
|
695
|
+
const credOpt = credFile ? `-c credential.helper='store --file=${credFile}' ` : '';
|
|
696
|
+
if (credFile) writeFile(credFile, `https://x-access-token:${ownerToken}@github.com\n`, { mode: 0o600 });
|
|
697
|
+
return { credFile, httpsUrl, credOpt };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function pushScaffold({ exec, log, root, repo, mode, cred }) {
|
|
701
|
+
if (cred.credFile) {
|
|
702
|
+
exec(`git -C ${root} ${cred.credOpt}push ${cred.httpsUrl} HEAD:main`);
|
|
703
|
+
log(` [scaffold] ${mode === 'adopt' ? 'pushed the adopt layer' : 'pushed'} to ${repo.owner}/${repo.name}@main via the owner's GitHub token (task 1002317)`);
|
|
704
|
+
} else {
|
|
705
|
+
exec(`git -C ${root} push -u origin main`);
|
|
706
|
+
log(` [scaffold] pushed ${mode === 'adopt' ? 'the adopt layer' : 'initial scaffold'} to ${repo.owner}/${repo.name}@main (SSH origin)`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// alreadyScaffolded is a LOCAL marker, and a scaffold whose push failed leaves it behind: the
|
|
711
|
+
// re-run skipped the never-done push, then wedged at step 2's `git pull --ff-only` against the
|
|
712
|
+
// still-empty remote (task 1002718, F12). Only a remote that ANSWERS with no main re-pushes — an
|
|
713
|
+
// unreachable one keeps the old skip, as it can't tell "never pushed" from "offline". Returns
|
|
714
|
+
// null when there is nothing to resume.
|
|
715
|
+
async function resumeUnpushedScaffold(inst, deps, { root, repo, mode }) {
|
|
716
|
+
const exec = deps.controlExec || deps.exec;
|
|
717
|
+
const writeFile = deps.controlWriteFile || deps.writeFile;
|
|
718
|
+
let cred = null;
|
|
719
|
+
let probe = { softFailed: true };
|
|
720
|
+
try {
|
|
721
|
+
// This runs on every re-provision of a LIVE instance, so a probe that cannot even start
|
|
722
|
+
// (token lookup, credential write) must keep the skip — never fail a healthy instance.
|
|
723
|
+
try {
|
|
724
|
+
cred = await scaffoldCredential(inst, repo, deps, writeFile);
|
|
725
|
+
probe = exec(`git -C ${root} ${cred.credOpt}ls-remote --heads ${cred.credFile ? cred.httpsUrl : 'origin'} main`, { allowFail: true });
|
|
726
|
+
} catch { deps.log(` [scaffold] could not probe ${repo.owner}/${repo.name} for main — keeping the skip`); }
|
|
727
|
+
if (probe.softFailed || String(probe.stdout || '').trim()) return null;
|
|
728
|
+
deps.log(` [scaffold] ${root} is scaffolded but ${repo.owner}/${repo.name} has no main — re-pushing the scaffold that never landed`);
|
|
729
|
+
pushScaffold({ exec, log: deps.log, root, repo, mode, cred });
|
|
730
|
+
return { ok: true, resumed: true, mode };
|
|
731
|
+
} finally {
|
|
732
|
+
if (cred && cred.credFile) exec(`rm -f ${cred.credFile}`, { allowFail: true });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
697
736
|
// ── private-repo deploy key (ADR 0176, task 1002735; walk finding N4) ────────────
|
|
698
737
|
|
|
699
738
|
function deployKeyTitle(inst) { return `cloudbongos-deploy-${inst.slug}`; }
|
|
@@ -43,7 +43,7 @@ async function teardownInstance(inst, deps) {
|
|
|
43
43
|
// Left for an operator (data is precious), naming ONLY what this row created —
|
|
44
44
|
// teardown now takes 'requested'/'error'. `port` + `domain` stay held: ADR 0181.
|
|
45
45
|
if (inst.db_name) log(` (left for the operator: dropdb ${dbName(inst)}, prune ${CONFIG.backupDir}/${dbName(inst)}-*.sql.gz)`);
|
|
46
|
-
} else if (apply &&
|
|
46
|
+
} else if (apply && inst.droplet_id && deps.doApi) {
|
|
47
47
|
await deps.doApi.deleteDroplet(inst.droplet_id);
|
|
48
48
|
}
|
|
49
49
|
parkAddress(inst, { controlExec, log, apply });
|
package/scripts/gds/provision.js
CHANGED
|
@@ -1393,12 +1393,14 @@ async function main() {
|
|
|
1393
1393
|
if (CONFIG.remoteHost && !CONFIG.remoteCwd) { console.error('PROVISION_REMOTE_HOST is set but PROVISION_REMOTE_CHECKOUT is not — name the co-hosting box\'s checkout dir (the old hardcoded default was removed, task 1002756).'); process.exit(2); }
|
|
1394
1394
|
const exec = makeExec({ apply, remoteHost: CONFIG.remoteHost, remoteCwd: CONFIG.remoteCwd, sshOpts: CONFIG.sshOpts });
|
|
1395
1395
|
const controlExec = makeExec({ apply });
|
|
1396
|
+
let doApiClient = null;
|
|
1396
1397
|
const deps = {
|
|
1397
1398
|
db: pool, provisioning, apply, privileged: CONFIG.privileged, log: console.log,
|
|
1398
1399
|
exec, controlExec,
|
|
1399
1400
|
writeFile: makeWriteFile({ apply, privileged: CONFIG.privileged, exec, remoteHost: CONFIG.remoteHost }),
|
|
1400
1401
|
controlWriteFile: makeWriteFile({ apply, privileged: CONFIG.privileged, exec: controlExec }),
|
|
1401
|
-
|
|
1402
|
+
// Built on first READ (task 1002718 F6): an --apply tick whose intents never call DO needs no DO_API_TOKEN.
|
|
1403
|
+
get doApi() { return DoApi ? (doApiClient = doApiClient || new DoApi({ dryRun: !apply, token: CONFIG.doToken, log: (m) => console.log(' ' + m) })) : null; },
|
|
1402
1404
|
// Cross-site identity (ADR 0141 §4). Null on a non-hub control plane → federation
|
|
1403
1405
|
// dormant; shouldFederate() then skips and the instance owns its auth (as today).
|
|
1404
1406
|
platformIdentity, hubKeys,
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.835'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/provision.mjs
CHANGED
|
@@ -566,15 +566,79 @@ await ta('scaffoldStandaloneRepo: idempotent — a repo that already carries pac
|
|
|
566
566
|
mkdirSync(join(tmp, 'demo'), { recursive: true });
|
|
567
567
|
writeFileSync(join(tmp, 'demo', 'package.json'), '{}\n'); // sign of a completed scaffold
|
|
568
568
|
const { deps, cmds } = recorder({ apply: true });
|
|
569
|
+
deps.exec = (cmd) => { cmds.push(cmd); return { ok: true, stdout: /ls-remote/.test(cmd) ? 'abc123\trefs/heads/main\n' : '' }; };
|
|
569
570
|
const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo' }, deps);
|
|
570
571
|
assert.equal(res.already, true);
|
|
571
|
-
assert.
|
|
572
|
+
assert.deepEqual(cmds.filter((c) => !/ls-remote/.test(c)), [], 'no init/build/push when already scaffolded AND the remote carries main');
|
|
572
573
|
} finally {
|
|
573
574
|
P.CONFIG.standaloneBase = origBase;
|
|
574
575
|
rmSync(tmp, { recursive: true, force: true });
|
|
575
576
|
}
|
|
576
577
|
});
|
|
577
578
|
|
|
579
|
+
// F12 (task 1002718): package.json is a LOCAL marker, so a scaffold whose push failed left it
|
|
580
|
+
// behind — the re-run skipped the never-done push and wedged at step 2's `git pull --ff-only`
|
|
581
|
+
// on the empty remote. The runner now asks the remote before skipping.
|
|
582
|
+
async function rerunScaffold({ apply = true, lsRemote, token = null, credWriteThrows = false }) {
|
|
583
|
+
const tmp = mkdtempSync(join(tmpdir(), 'scaffold-resume-'));
|
|
584
|
+
const origBase = P.CONFIG.standaloneBase;
|
|
585
|
+
P.CONFIG.standaloneBase = tmp;
|
|
586
|
+
try {
|
|
587
|
+
mkdirSync(join(tmp, 'demo'), { recursive: true });
|
|
588
|
+
writeFileSync(join(tmp, 'demo', 'package.json'), '{}\n');
|
|
589
|
+
const { deps, cmds } = recorder({ apply });
|
|
590
|
+
const writes = [];
|
|
591
|
+
deps.exec = (cmd) => { cmds.push(cmd); return /ls-remote/.test(cmd) ? lsRemote : { ok: true, stdout: '' }; };
|
|
592
|
+
deps.writeFile = (p, body, opts) => {
|
|
593
|
+
if (credWriteThrows && /bongos-gitcred/.test(p)) throw new Error('EACCES: tmp not writable');
|
|
594
|
+
writes.push({ p, body, opts }); return { ok: true };
|
|
595
|
+
};
|
|
596
|
+
if (token) deps.getGithubToken = async () => ({ token, scope: 'repo' });
|
|
597
|
+
const res = await P.scaffoldStandaloneRepo({ slug: 'demo', hosting_shape: 'standalone', target_ref: 'example-owner/demo', owner_builder_id: 7 }, deps);
|
|
598
|
+
return { res, cmds, writes };
|
|
599
|
+
} finally {
|
|
600
|
+
P.CONFIG.standaloneBase = origBase;
|
|
601
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
await ta('scaffoldStandaloneRepo: a scaffold whose push never landed RE-PUSHES on re-run (remote has no main) — F12, task 1002718', async () => {
|
|
606
|
+
const { res, cmds } = await rerunScaffold({ lsRemote: { ok: true, stdout: '' } });
|
|
607
|
+
const joined = cmds.join('\n');
|
|
608
|
+
assert.equal(res.resumed, true);
|
|
609
|
+
assert.match(joined, /ls-remote --heads [^\n]*origin main/, 'asks the remote whether main exists');
|
|
610
|
+
assert.match(joined, /git -C [^\n]* push -u origin main/, 're-pushes the scaffold commit');
|
|
611
|
+
assert.ok(!/init\.js|package-core|commit -m|--force/.test(joined), 'does NOT re-scaffold, re-commit or force — only the owed push');
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
await ta('scaffoldStandaloneRepo: the resume push rides the owner token and removes its credential file (F12)', async () => {
|
|
615
|
+
const { cmds, writes } = await rerunScaffold({ lsRemote: { ok: true, stdout: '' }, token: 'gho_RESUME_SECRET' });
|
|
616
|
+
const joined = cmds.join('\n');
|
|
617
|
+
assert.ok(!joined.includes('gho_RESUME_SECRET'), 'token never appears in a command string');
|
|
618
|
+
assert.match(joined, /credential\.helper='store --file=[^']*bongos-gitcred-demo[^']*' ls-remote --heads https:\/\/x-access-token@github\.com\/example-owner\/demo\.git main/);
|
|
619
|
+
assert.match(joined, /credential\.helper='store --file=[^']*bongos-gitcred-demo[^']*' push https:\/\/x-access-token@github\.com\/example-owner\/demo\.git HEAD:main/);
|
|
620
|
+
assert.equal(writes.find((w) => /bongos-gitcred-demo/.test(w.p)).opts.mode, 0o600);
|
|
621
|
+
assert.match(cmds[cmds.length - 1], /rm -f [^\n]*bongos-gitcred-demo/, 'credential file removed last');
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
await ta('scaffoldStandaloneRepo: an UNREACHABLE remote keeps the old skip — only a confirmed-empty remote re-pushes (F12)', async () => {
|
|
625
|
+
const { res, cmds } = await rerunScaffold({ lsRemote: { ok: true, stdout: '', softFailed: true } });
|
|
626
|
+
assert.equal(res.already, true);
|
|
627
|
+
assert.ok(!cmds.some((c) => /push/.test(c)), cmds.join(' | '));
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
await ta('scaffoldStandaloneRepo: a probe that cannot start (credential write throws) keeps the skip — a live instance is never failed by it (F12)', async () => {
|
|
631
|
+
const { res, cmds } = await rerunScaffold({ lsRemote: { ok: true, stdout: '' }, token: 'gho_X', credWriteThrows: true });
|
|
632
|
+
assert.equal(res.already, true);
|
|
633
|
+
assert.ok(!cmds.some((c) => /push|ls-remote/.test(c)), cmds.join(' | '));
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
await ta('scaffoldStandaloneRepo: dry-run of an already-scaffolded repo probes nothing (F12)', async () => {
|
|
637
|
+
const { res, cmds } = await rerunScaffold({ apply: false, lsRemote: { ok: true, stdout: '' } });
|
|
638
|
+
assert.equal(res.already, true);
|
|
639
|
+
assert.deepEqual(cmds, []);
|
|
640
|
+
});
|
|
641
|
+
|
|
578
642
|
// ── ADR 0134 (task 2090): registry core-install path + fail-soft to vendored ──
|
|
579
643
|
// Runs the scaffold against a FRESH standaloneBase (no package.json → it proceeds) with
|
|
580
644
|
// an injected env, and asserts which core-resolution shape it chose from the commands.
|
|
@@ -1499,6 +1563,30 @@ await ta('teardownInstance(dedicated): no systemd steps at all, so no reset-fail
|
|
|
1499
1563
|
assert.ok(!cmds.some((c) => /reset-failed|daemon-reload|disable --now/.test(c)), cmds.join(' | '));
|
|
1500
1564
|
});
|
|
1501
1565
|
|
|
1566
|
+
// F6 (task 1002718): main() builds deps.doApi on first READ, so a caller must only read it
|
|
1567
|
+
// on a path that really calls DigitalOcean — a throwing getter proves the others never do.
|
|
1568
|
+
function doApiTripwire(deps) {
|
|
1569
|
+
Object.defineProperty(deps, 'doApi', { get() { throw new Error('read deps.doApi on a path that makes no DO call'); } });
|
|
1570
|
+
return deps;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
await ta('teardownInstance(dedicated, no droplet_id) under --apply never reads deps.doApi (F6)', async () => {
|
|
1574
|
+
const { deps } = recorder({ apply: true });
|
|
1575
|
+
await P.teardownInstance({ ...coTenant, hosting_shape: 'dedicated', droplet_id: null }, doApiTripwire(deps));
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
await ta('provisionInstance(co-tenant) under --apply never reads deps.doApi (F6)', async () => {
|
|
1579
|
+
const { deps } = recorder({ apply: true });
|
|
1580
|
+
const r = await P.provisionInstance(coTenant, doApiTripwire(deps));
|
|
1581
|
+
assert.ok(!/deps\.doApi/.test(String(r && r.error)), String(r && r.error));
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
t('main() constructs the DO client lazily — no DO_API_TOKEN needed until a DO call (F6)', () => {
|
|
1585
|
+
const src = readFileSync(new URL('../scripts/gds/provision.js', import.meta.url), 'utf8');
|
|
1586
|
+
assert.match(src, /get doApi\(\)/, 'deps.doApi is a getter');
|
|
1587
|
+
assert.ok(!/doApi:\s*DoApi \? new DoApi/.test(src), 'no eager construction at deps build');
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1502
1590
|
await ta('teardownInstance(default): NO sudo (bare, unchanged)', async () => {
|
|
1503
1591
|
const { deps, cmds } = recorder();
|
|
1504
1592
|
await P.teardownInstance(coTenant, deps);
|