@bongos/core 1.21.93 → 1.21.95
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 +63 -38
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +9 -2
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/api/openapi.json +112 -9
- package/docs/api-reference.md +6 -3
- package/docs/copy-inventory.md +354 -354
- package/docs/copy-registry.json +420 -420
- package/docs/design/project-startup-direction.md +15 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +963 -992
- package/modules/provisioning/github-setup.js +129 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/provisioning.js +3 -0
- package/modules/provisioning/routes/callback-page.js +83 -8
- package/modules/provisioning/routes/github-setup.js +221 -0
- package/modules/provisioning/routes/provisioning.js +11 -45
- package/modules/public-landing/public/projects.html +207 -66
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/oauth-secret.js +4 -2
- package/scripts/gds/provision-github-setup.js +148 -0
- package/scripts/gds/provision.js +19 -5
- package/src/module-api.js +1 -1
- package/tests/github_handback_any_device.mjs +340 -0
- package/tests/github_one_trip.mjs +297 -0
- package/tests/projects_hub.mjs +6 -2
- package/tests/projects_hub_dns_ready.mjs +2 -1
- package/tests/projects_hub_pre_uat.mjs +17 -9
- package/tests/provision.mjs +51 -9
- package/tests/provisioning_callback_page.mjs +3 -2
- package/tests/provisioning_github_app_routes.mjs +103 -12
- package/tests/wizard_draft_resume.mjs +4 -0
- package/tests/wizard_github_feedback.mjs +9 -5
|
@@ -601,6 +601,8 @@ body:has(#view-new:not([hidden])) footer.foot{position:relative;z-index:1;backgr
|
|
|
601
601
|
display:inline-flex;align-items:center;justify-content:center;font-family:var(--font-body);font-size:13px;letter-spacing:0;}
|
|
602
602
|
#panel-done .humanSteps>li.sdone::before{content:"✓";text-transform:none;color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent);border:1px solid var(--ok);}
|
|
603
603
|
#panel-done .humanSteps>li.slocked::before{border:1px solid var(--rule-soft);}
|
|
604
|
+
/* the optional step (command-line sign-in, task 1004649): a dashed ring until it ticks */
|
|
605
|
+
#panel-done .humanSteps>li.soptional:not(.sdone)::before{border-style:dashed;}
|
|
604
606
|
#panel-done .humanSteps b{font-size:15px;line-height:24px;}
|
|
605
607
|
.alive{margin-top:18px;padding-top:18px;border-top:1px solid var(--rule);}
|
|
606
608
|
#view-new .aliveH{margin:0;font-family:var(--font-display);font-size:28px;font-weight:200;letter-spacing:-.02em;line-height:1.2;text-transform:none;color:var(--ink);}
|
|
@@ -4581,6 +4583,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4581
4583
|
survives the GitHub round-trip; the server, not this page, names the terms version. */
|
|
4582
4584
|
var DRAFT_KEY = 'cb-create-draft-v10';
|
|
4583
4585
|
var CREATED_KEY = 'cb-created-instance';
|
|
4586
|
+
/* the project "Start another project" set down (task 1004650): the server's resume
|
|
4587
|
+
read does not pull this tab back to it */
|
|
4588
|
+
var DECLINED_KEY = 'cb-resume-declined';
|
|
4584
4589
|
/* `description` + `teamShape` are the optional About step (task 1002334) —
|
|
4585
4590
|
both legitimately stay empty, and empty is what the create request omits. */
|
|
4586
4591
|
/* `modules` is the picker's answer (task 1002339) and is THREE-VALUED like
|
|
@@ -6603,7 +6608,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6603
6608
|
return '<a href="' + (instanceId ? '?view=manage&id=' + encodeURIComponent(instanceId) : '?view=mine') +
|
|
6604
6609
|
'">' + label + '</a>';
|
|
6605
6610
|
}
|
|
6606
|
-
var live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null };
|
|
6611
|
+
var live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null };
|
|
6607
6612
|
var planPromises = {}; /* mode|domain → Promise<steps[]> */
|
|
6608
6613
|
var lastStepsHtml = '';
|
|
6609
6614
|
var lastProgHtml = ''; /* same change-only contract, for the progress strip */
|
|
@@ -6632,6 +6637,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6632
6637
|
(task 1002562) — the owner no longer runs `bongos init --adopt` here. */
|
|
6633
6638
|
function renderStepGithubRepo() {
|
|
6634
6639
|
var landed = live.instStatus === 'active';
|
|
6640
|
+
/* the one-page checklist (task 1004649) shows only the GitHub steps the founder
|
|
6641
|
+
takes; a repo the platform made or adopted needs nothing, so it is not a row */
|
|
6642
|
+
if (state.mode === 'adopt' || state.repoAutoCreated || landed) return '';
|
|
6635
6643
|
var repoLabel = state.owner && state.repo ? '<strong>' + esc(state.owner + '/' + state.repo) + '</strong>' : 'your new repository';
|
|
6636
6644
|
/* Naming a repo without linking it stranded the owner on exactly the path where the
|
|
6637
6645
|
platform did the work (task 1002855). Adopt links the repo the owner picked;
|
|
@@ -6697,27 +6705,34 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6697
6705
|
else window.location.assign(url);
|
|
6698
6706
|
}
|
|
6699
6707
|
|
|
6708
|
+
/* The four GitHub steps of part 03, in the mockup's words and order (task 1004649,
|
|
6709
|
+
docs/design/mocks/day-night-rooms/): Approve the <name> app on GitHub · Install it
|
|
6710
|
+
on your repo · Builders can sign in · Command-line sign-in is on. One click starts
|
|
6711
|
+
ONE trip — approve, straight on to installing, back here — and every tick is what
|
|
6712
|
+
the server verified (live.setup, the status poll's `checklist`). Every value that
|
|
6713
|
+
reaches the markup goes through esc(). */
|
|
6714
|
+
function ghStep(key) { return live.setup && live.setup[key]; }
|
|
6715
|
+
function appTitle() { return 'Approve the ' + esc(state.name || state.slug || 'project') + ' app on GitHub'; }
|
|
6716
|
+
|
|
6700
6717
|
/* oauth-app — the ADR 0133 one-click GitHub-App manifest flow is the primary path
|
|
6701
6718
|
(gap 3 / idea 1000676); locked until a domain exists (the App needs the real
|
|
6702
6719
|
callback host — the server refuses domainless with domain_required). */
|
|
6703
6720
|
function renderStepOauthApp() {
|
|
6704
6721
|
var domain = resolvedDomain();
|
|
6705
6722
|
if (!domain) {
|
|
6706
|
-
return '<li class="slocked" data-onboard-step="oauth-app"><b>
|
|
6723
|
+
return '<li class="slocked" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>Unlocks once your project has an address — sign-in needs a real domain. Attach one any time from ' + projectPageLink('this project’s own page') + '.</p></li>';
|
|
6724
|
+
}
|
|
6725
|
+
/* GitHub made the app and the platform placed its keys: the slug is recorded only
|
|
6726
|
+
after the placement (the runner), so a known slug outside a failure is the tick —
|
|
6727
|
+
a fact, whatever the venue says about starting a NEW trip */
|
|
6728
|
+
if (live.manifest !== 'error' && live.manifest !== 'expired' && (ghStep('approve') === 'done' || live.appSlug)) {
|
|
6729
|
+
return '<li class="sdone" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>GitHub made the app, and its keys are on your project.</p></li>';
|
|
6707
6730
|
}
|
|
6708
6731
|
/* honesty about the venue (owner walk, 2026-08-12): GitHub rejects app
|
|
6709
6732
|
manifests whose hook URL isn't publicly reachable, so a locally-hosted
|
|
6710
6733
|
platform can't complete the one-click flow — say it BEFORE the bounce. */
|
|
6711
6734
|
if (!apexBase()) {
|
|
6712
|
-
return '<li class="slocked" data-onboard-step="oauth-app"><b>
|
|
6713
|
-
}
|
|
6714
|
-
if (live.manifest === 'done') {
|
|
6715
|
-
/* the wait ends HERE now, not on the callback page (task 1004548): once the
|
|
6716
|
-
project answers too, the step carries the door itself */
|
|
6717
|
-
var open = live.instStatus === 'active' && live.addrReady;
|
|
6718
|
-
return '<li class="sdone" data-onboard-step="oauth-app"><b>GitHub sign-in is wired</b><p>Credentials placed and verified — sign-in on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code> is live.' +
|
|
6719
|
-
(open ? '' : ' The door below opens once your project is answering.') + '</p>' +
|
|
6720
|
-
(open ? '<a class="ghbtn" href="https://' + esc(domain) + '" target="_blank" rel="noopener">Open ' + esc(domain) + ' →</a>' : '') + '</li>';
|
|
6735
|
+
return '<li class="slocked" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>This platform is running on a local address, and GitHub refuses app setups it can’t reach over the public internet — so the one-click flow can’t complete here. On a publicly-addressed platform this is one click.</p>' + manualOauthDetails() + '</li>';
|
|
6721
6736
|
}
|
|
6722
6737
|
if (live.manifest === 'error' || live.manifest === 'expired') {
|
|
6723
6738
|
/* a plain sentence first; the raw route text folds under Technical
|
|
@@ -6731,86 +6746,98 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6731
6746
|
else if (live.manifestCode === 'domain_required') snag = 'This project needs an address first — attach one from ' + projectPageLink('its own page') + ', then come back to this step.';
|
|
6732
6747
|
else if (live.manifestFailure === 'sign_in_unconfirmed') {
|
|
6733
6748
|
var appLink = live.appSlug ? '<a href="https://github.com/settings/apps/' + esc(live.appSlug) + '" target="_blank" rel="noopener">your app on GitHub</a>' : 'the app on your GitHub account';
|
|
6734
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6749
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>GitHub approved the app and the platform placed its credentials on your project, but your project didn’t confirm that sign-in works. Trying again would make a second app on your GitHub account and is unlikely to fix this — it needs a look from the platform team. <a href="/contact">Contact us</a> and mention ' + appLink + '.</p>' +
|
|
6735
6750
|
progressMarkup(1, 1, 'Stopped: your project didn’t confirm sign-in.', 'isErr', 'signin') +
|
|
6736
6751
|
'<button type="button" class="linklike" data-action="manifest-start">Try again anyway</button></li>';
|
|
6737
6752
|
}
|
|
6738
6753
|
else if (live.manifestFailure === 'finish_failed') snag = 'GitHub approved the app, but the platform couldn’t finish setting up sign-in on your project. The app GitHub made is still on your account — delete it first (GitHub → Settings → Developer settings → GitHub Apps) so you don’t end up with two, then try again.';
|
|
6739
6754
|
else { snag = 'GitHub setup didn’t start. Nothing was changed on GitHub — try again.'; raw = live.manifestMsg; }
|
|
6740
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6755
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>' + snag + '</p>' +
|
|
6741
6756
|
(raw ? '<details><summary>Technical details</summary><p>' + esc(raw) + '</p></details>' : '') +
|
|
6742
|
-
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + '
|
|
6757
|
+
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + 'Connect GitHub ↗</button>' + manualOauthDetails() + '</li>';
|
|
6743
6758
|
}
|
|
6744
6759
|
if (live.manifest === 'code_received' || live.manifest === 'converting') {
|
|
6745
|
-
/* GitHub's half is done — say so, rather than keep telling the
|
|
6746
|
-
to approve an app they just approved */
|
|
6760
|
+
/* GitHub's half of the approval is done — say so, rather than keep telling the
|
|
6761
|
+
founder to approve an app they just approved */
|
|
6747
6762
|
autoOpenArmed = true;
|
|
6748
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6749
|
-
progressMarkup(1, 1, 'Placing
|
|
6763
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>Approved on GitHub. The platform is placing the app’s keys on your project, and the GitHub tab goes on to installing it on your repo. This step ticks itself.</p>' +
|
|
6764
|
+
progressMarkup(1, 1, 'Placing the app’s keys on your project…', '', 'signin') + autoOpenTick() + '</li>';
|
|
6750
6765
|
}
|
|
6751
6766
|
if (live.manifest) { /* pending — GitHub holds the manifest, nobody has approved it yet */
|
|
6752
6767
|
autoOpenArmed = true;
|
|
6753
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6768
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>Waiting on GitHub — approve the app in the tab that opened. No GitHub tab? It may have opened behind this window or in another one. Once approved, GitHub takes you straight on to installing it on your repo, then back here; every step ticks itself.</p>' +
|
|
6754
6769
|
progressMarkup(0, 0, 'Waiting for your approval on GitHub…', '', 'signin') + autoOpenTick() +
|
|
6755
6770
|
(manifestStale ? '<button type="button" class="linklike" data-action="manifest-start">Didn’t get to GitHub? Start the setup again</button>' : '') + '</li>';
|
|
6756
6771
|
}
|
|
6757
|
-
return '<li data-onboard-step="oauth-app"><b>
|
|
6758
|
-
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + '
|
|
6772
|
+
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>One trip to GitHub: approve the app, install it on your repo, and you land back here. No keys to copy, and the secret never leaves the server side.</p>' +
|
|
6773
|
+
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + 'Connect GitHub ↗</button>' + manualOauthDetails() + '</li>';
|
|
6759
6774
|
}
|
|
6760
6775
|
|
|
6761
|
-
/* device-flow —
|
|
6762
|
-
GitHub creates a manifest-born App with Device Flow OFF and offers no
|
|
6763
|
-
|
|
6764
|
-
the
|
|
6776
|
+
/* device-flow — "Command-line sign-in is on", the OPTIONAL fourth step (task 1004649).
|
|
6777
|
+
GitHub creates a manifest-born App with Device Flow OFF and offers no manifest
|
|
6778
|
+
field and no API to switch it on (the spike in task 1004649), so only the founder's
|
|
6779
|
+
own tick in the app's settings can — and the runner ticks this step when GitHub
|
|
6780
|
+
starts a device sign-in for the app. It never holds the project back: web sign-in
|
|
6781
|
+
works without it, and the CLI falls back to a web token meanwhile. */
|
|
6765
6782
|
function renderStepDeviceFlow() {
|
|
6783
|
+
var title = '<b>Command-line sign-in is on</b>';
|
|
6784
|
+
if (ghStep('device_flow') === 'done') {
|
|
6785
|
+
return '<li class="sdone soptional" data-onboard-step="device-flow">' + title + '<p>Builders can sign in from a terminal too — the <code style="font-family:var(--font-mono)">bongos setup</code> command builders run on their own machine.</p></li>';
|
|
6786
|
+
}
|
|
6766
6787
|
if (live.manifest !== 'done') {
|
|
6767
|
-
return '<li class="slocked" data-onboard-step="device-flow"
|
|
6788
|
+
return '<li class="slocked soptional" data-onboard-step="device-flow">' + title + '<p>Optional — turn it on later. It lets builders sign in from a terminal, with the <code style="font-family:var(--font-mono)">bongos setup</code> command they run on their own machine; web sign-in works without it.</p></li>';
|
|
6768
6789
|
}
|
|
6769
6790
|
var appUrl = live.appSlug ? 'https://github.com/settings/apps/' + esc(live.appSlug) : null;
|
|
6770
|
-
return '<li data-onboard-step="device-flow"
|
|
6791
|
+
return '<li class="soptional" data-onboard-step="device-flow">' + title + '<p>Optional — turn it on later. GitHub leaves it off on new apps and lets only you switch it on: ' +
|
|
6771
6792
|
(appUrl
|
|
6772
|
-
? '
|
|
6793
|
+
? 'open <a href="' + appUrl + '" target="_blank" rel="noopener">your app’s settings</a>, check <strong>Enable Device Flow</strong>, and save. '
|
|
6773
6794
|
: 'GitHub → Settings → Developer settings → GitHub Apps → your app → check <strong>Enable Device Flow</strong> → save. ') +
|
|
6774
|
-
'
|
|
6795
|
+
'We tick this when GitHub has it on. Until then builders sign in on the web, and terminal sign-in falls back to a web token from the hall’s Settings page.</p></li>';
|
|
6775
6796
|
}
|
|
6776
6797
|
|
|
6777
|
-
/* app-install — the ADR 0055 publish credential (task
|
|
6778
|
-
|
|
6779
|
-
|
|
6798
|
+
/* app-install — "Install it on your repo": the ADR 0055 publish credential (task
|
|
6799
|
+
1003049). The same trip carries the founder here straight from the approval (task
|
|
6800
|
+
1004649); this step ticks only once the runner has asked GitHub, with the app's own
|
|
6801
|
+
key, and GitHub said the app is on this repo. The button is the way back for a
|
|
6802
|
+
founder who returned without installing. */
|
|
6780
6803
|
function renderStepAppInstall() {
|
|
6781
|
-
|
|
6782
|
-
|
|
6804
|
+
var title = '<b>Install it on your repo</b>';
|
|
6805
|
+
if (ghStep('install') === 'done') {
|
|
6806
|
+
return '<li class="sdone" data-onboard-step="app-install">' + title + '<p>The app is on ' + (state.owner && state.repo ? esc(state.owner + '/' + state.repo) : 'your repo') + ', so finished work can merge and ship itself.</p></li>';
|
|
6783
6807
|
}
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
'
|
|
6808
|
+
if (!live.appSlug) {
|
|
6809
|
+
return '<li class="slocked" data-onboard-step="app-install">' + title + '<p>Comes straight after the approval, on the same trip: pick your repo and you land back here.</p></li>';
|
|
6810
|
+
}
|
|
6811
|
+
var installUrl = live.installUrl || ('https://github.com/apps/' + encodeURIComponent(live.appSlug) + '/installations/new');
|
|
6812
|
+
return '<li data-onboard-step="app-install">' + title + '<p>Your GitHub tab goes here next. We check with GitHub that the app is on your repo, and this step ticks itself. Came back without installing it?</p>' +
|
|
6813
|
+
'<a class="ghbtn" href="' + esc(installUrl) + '" target="_blank" rel="noopener">' + GH_ICON + 'Install it on your repo ↗</a></li>';
|
|
6790
6814
|
}
|
|
6791
6815
|
|
|
6816
|
+
/* first-sign-in — "Builders can sign in": ticks when the platform has seen sign-in
|
|
6817
|
+
work on the project (the runner's auth_configured check, the manifest 'done'). The
|
|
6818
|
+
door opens only when the project ANSWERS too: a link to an address that does not
|
|
6819
|
+
resolve yet is the worst first impression a founder can get (task 1003232). */
|
|
6792
6820
|
function renderStepFirstSignIn() {
|
|
6793
6821
|
var domain = resolvedDomain();
|
|
6822
|
+
var title = '<b>Builders can sign in</b>';
|
|
6794
6823
|
if (!domain) {
|
|
6795
|
-
return '<li class="slocked" data-onboard-step="first-sign-in"
|
|
6824
|
+
return '<li class="slocked" data-onboard-step="first-sign-in">' + title + '<p>Unlocks with an address — then you open it, sign in with GitHub once, and take the owner’s seat (the founding Archon).</p></li>';
|
|
6796
6825
|
}
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
impression a founder can get of their own project (task 1003232) */
|
|
6800
|
-
if (live.instStatus !== 'active' || !live.addrReady || live.manifest !== 'done') {
|
|
6801
|
-
return '<li class="slocked" data-onboard-step="first-sign-in"><b>First sign-in</b><p>Unlocks once your project is answering and sign-in above is wired. Then you’ll open <code style="font-family:var(--font-mono)">' + esc(domain) + '</code>, sign in with GitHub once, and be seated as the owner — the founding Archon.</p></li>';
|
|
6826
|
+
if (live.manifest !== 'done') {
|
|
6827
|
+
return '<li class="slocked" data-onboard-step="first-sign-in">' + title + '<p>Ticks once the platform has seen sign-in work on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code>.</p></li>';
|
|
6802
6828
|
}
|
|
6803
|
-
|
|
6829
|
+
var open = live.instStatus === 'active' && live.addrReady;
|
|
6830
|
+
return '<li class="sdone" data-onboard-step="first-sign-in">' + title + '<p>Sign-in works on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code>.' +
|
|
6831
|
+
(open ? ' Open it and sign in with GitHub once to take the owner’s seat — the founding Archon.' : ' The door opens here once your project is answering.') + '</p>' +
|
|
6832
|
+
(open ? '<a class="ghbtn" href="https://' + esc(domain) + '" target="_blank" rel="noopener">Open ' + esc(domain) + ' →</a>' : '') + '</li>';
|
|
6804
6833
|
}
|
|
6805
6834
|
|
|
6835
|
+
/* enter-hall — not a row of the GitHub checklist (the "is alive" block below carries
|
|
6836
|
+
the way in); kept only for a project with no address, whose hall is local */
|
|
6806
6837
|
function renderStepEnterHall() {
|
|
6807
6838
|
var domain = resolvedDomain();
|
|
6808
|
-
if (domain
|
|
6809
|
-
|
|
6810
|
-
}
|
|
6811
|
-
return domain
|
|
6812
|
-
? '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>Your project’s home — open <a href="https://' + esc(domain) + '/builders" target="_blank" rel="noopener"><code style="font-family:var(--font-mono)">' + esc(domain) + '/builders</code></a> and start building.</p></li>'
|
|
6813
|
-
: '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>No address yet — run <code style="font-family:var(--font-mono)">bongos dev</code> in your repo checkout to start the local hall on your machine.</p></li>';
|
|
6839
|
+
if (domain) return '';
|
|
6840
|
+
return '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>No address yet — run <code style="font-family:var(--font-mono)">bongos dev</code> in your repo checkout to start the local hall on your machine (on an address it lives at /builders).</p></li>';
|
|
6814
6841
|
}
|
|
6815
6842
|
|
|
6816
6843
|
var STEP_RENDERERS = {
|
|
@@ -6828,9 +6855,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6828
6855
|
control-plane wizard cannot reach. Spun out per the direction's §9
|
|
6829
6856
|
delivery-split as task 1002739. */
|
|
6830
6857
|
|
|
6858
|
+
/* the mockup's order: the repo (only when the founder must make it), approve,
|
|
6859
|
+
install, sign-in, command line; a plan step this page has no row for goes last */
|
|
6860
|
+
var STEP_ORDER = ['github-repo', 'oauth-app', 'app-install', 'first-sign-in', 'device-flow', 'enter-hall'];
|
|
6861
|
+
function stepRank(s) { var i = STEP_ORDER.indexOf(s.key); return i < 0 ? STEP_ORDER.length : i; }
|
|
6831
6862
|
function renderSteps() {
|
|
6832
6863
|
fetchPlan().then(function (steps) {
|
|
6833
|
-
var html = steps.filter(function (s) { return s && s.kind === 'human'; }).map(function (s) {
|
|
6864
|
+
var html = steps.filter(function (s) { return s && s.kind === 'human'; }).slice().sort(function (a, b) { return stepRank(a) - stepRank(b); }).map(function (s) {
|
|
6834
6865
|
var r = STEP_RENDERERS[s.key];
|
|
6835
6866
|
return r ? r(s) : '<li data-onboard-step="' + esc(s.key) + '"><b>' + esc(s.label || s.key) + '</b></li>';
|
|
6836
6867
|
}).join('');
|
|
@@ -6898,18 +6929,36 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6898
6929
|
try { localStorage.setItem(AUTO_OPEN_KEY, box.checked ? '1' : '0'); } catch (e) {}
|
|
6899
6930
|
});
|
|
6900
6931
|
|
|
6901
|
-
/* ── C8: the wizard's polls run only on stage,
|
|
6902
|
-
Off-view
|
|
6903
|
-
|
|
6932
|
+
/* ── C8: the wizard's polls run only while the create view is on stage, like the
|
|
6933
|
+
sky. Off-view a due tick parks itself instead of fetching; enter() resumes it.
|
|
6934
|
+
A HIDDEN tab is different (task 1004650): that is exactly when the founder is on
|
|
6935
|
+
GitHub, in another tab or on their phone, so the polls keep checking there at a
|
|
6936
|
+
slower pace (HIDDEN_POLL_MS), and the moment the tab is seen again a waiting tick
|
|
6937
|
+
runs at once — the laptop's checklist ticks on its own. ── */
|
|
6938
|
+
var HIDDEN_POLL_MS = 20000;
|
|
6904
6939
|
function wizPollActive() {
|
|
6905
|
-
return
|
|
6940
|
+
return currentView === 'new';
|
|
6941
|
+
}
|
|
6942
|
+
/* the next tick's wait: the poll's own pace while the tab is seen, the slow one while hidden */
|
|
6943
|
+
function wizPollDelay(ms) {
|
|
6944
|
+
return document.hidden ? Math.max(ms, HIDDEN_POLL_MS) : ms;
|
|
6906
6945
|
}
|
|
6907
6946
|
function resumePolls() {
|
|
6908
6947
|
if (watchPending && instanceId) { watchPending = false; startProgress(instanceId); }
|
|
6909
6948
|
if (manifestPending && manifestState) { manifestPending = false; pollManifest(manifestState); }
|
|
6910
6949
|
}
|
|
6950
|
+
/* each poll's waiting tick, so a return to the tab can run it now instead of later.
|
|
6951
|
+
Only a WAITING tick is run (its timer is set): one mid-fetch re-arms itself, and
|
|
6952
|
+
running it again would start a second chain. */
|
|
6953
|
+
var progressKick = null, manifestKick = null;
|
|
6954
|
+
function kickPolls() {
|
|
6955
|
+
if (pollTimer && progressKick) { clearTimeout(pollTimer); pollTimer = null; progressKick(); }
|
|
6956
|
+
if (manifestPollTimer && manifestKick) { clearTimeout(manifestPollTimer); manifestPollTimer = null; manifestKick(); }
|
|
6957
|
+
}
|
|
6911
6958
|
document.addEventListener('visibilitychange', function () {
|
|
6912
|
-
if (wizPollActive())
|
|
6959
|
+
if (document.hidden || !wizPollActive()) return;
|
|
6960
|
+
resumePolls();
|
|
6961
|
+
kickPolls();
|
|
6913
6962
|
});
|
|
6914
6963
|
|
|
6915
6964
|
/* ── GitHub-App manifest flow (ADR 0133): mint a one-time state for OUR instance,
|
|
@@ -6929,6 +6978,17 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6929
6978
|
var handbackState = (function () {
|
|
6930
6979
|
try { var m = /[#&]ghstate=([0-9a-f]{16,128})/i.exec(location.hash || ''); return m ? m[1] : null; } catch (e) { return null; }
|
|
6931
6980
|
})();
|
|
6981
|
+
/* …and the PROJECT it is for (task 1004650): the return route resolves the state on
|
|
6982
|
+
the server and lands its owner on ?view=new&id=<project>, so a phone, another
|
|
6983
|
+
window or a fresh tab reaches the right project's part 03 with nothing in this
|
|
6984
|
+
tab's storage. Read once at load, for the same reason as the state above. */
|
|
6985
|
+
var handbackProject = (function () {
|
|
6986
|
+
try {
|
|
6987
|
+
var q = new URLSearchParams(location.search || '');
|
|
6988
|
+
var id = q.get('view') === 'new' ? q.get('id') : null;
|
|
6989
|
+
return id && /^\d{1,12}$/.test(id) ? id : null;
|
|
6990
|
+
} catch (e) { return null; }
|
|
6991
|
+
})();
|
|
6932
6992
|
/* the manifest leg survives a reload: token + last status ride the CREATED
|
|
6933
6993
|
record, so enter() re-seeds the step instead of re-offering "Set up
|
|
6934
6994
|
GitHub sign-in" for an app the founder already approved (task 1003232) */
|
|
@@ -6991,6 +7051,22 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6991
7051
|
function manifestInFlight() {
|
|
6992
7052
|
return live.manifest === 'pending' || live.manifest === 'code_received' || live.manifest === 'converting';
|
|
6993
7053
|
}
|
|
7054
|
+
/* the checklist the server derived (task 1004649) — change-only; true when it moved.
|
|
7055
|
+
The install link rides only on a state-keyed read; the owner's read keeps the last. */
|
|
7056
|
+
function seedSetup(checklist, installUrl) {
|
|
7057
|
+
if (!checklist || typeof checklist !== 'object') return false;
|
|
7058
|
+
var next = JSON.stringify(checklist), was = live.setup ? JSON.stringify(live.setup) : '';
|
|
7059
|
+
var url = typeof installUrl === 'string' && /^https:\/\/github\.com\/apps\//.test(installUrl) ? installUrl : null;
|
|
7060
|
+
var urlMoved = !!url && url !== live.installUrl && (!live.installUrl || /[?&]state=/.test(url) || !/[?&]state=/.test(live.installUrl));
|
|
7061
|
+
if (next === was && !urlMoved) return false;
|
|
7062
|
+
live.setup = checklist;
|
|
7063
|
+
if (urlMoved) live.installUrl = url;
|
|
7064
|
+
return true;
|
|
7065
|
+
}
|
|
7066
|
+
/* still worth watching after sign-in: the install not verified yet, while the runner checks */
|
|
7067
|
+
function setupOpen() {
|
|
7068
|
+
return !!(live.setup && !live.setup.connected && live.manifest === 'done');
|
|
7069
|
+
}
|
|
6994
7070
|
function noteManifestWait() {
|
|
6995
7071
|
if (live.manifest === 'pending' && !manifestStale && manifestStartedAt && (Date.now() - manifestStartedAt) > 90000) { manifestStale = true; renderSteps(); }
|
|
6996
7072
|
}
|
|
@@ -7001,19 +7077,21 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7001
7077
|
function tick() {
|
|
7002
7078
|
if (myGen !== manifestGen) return;
|
|
7003
7079
|
if (!wizPollActive()) { manifestPending = true; stopManifestPoll(); return; }
|
|
7080
|
+
manifestKick = tick;
|
|
7004
7081
|
fetch(API + '/provisioning/github-app/status?state=' + encodeURIComponent(stateToken))
|
|
7005
7082
|
.then(function (res) { if (!res.ok) throw new Error('soft'); return res.json(); })
|
|
7006
7083
|
.then(function (d) {
|
|
7007
7084
|
if (myGen !== manifestGen) return;
|
|
7008
7085
|
if (d.app_slug && d.app_slug !== live.appSlug) { live.appSlug = d.app_slug; rememberManifest(undefined, undefined, d.app_slug); renderSteps(); }
|
|
7086
|
+
if (seedSetup(d.checklist, d.install_url)) renderSteps(); /* the server-verified ticks (task 1004649) */
|
|
7009
7087
|
var next = d.ready ? 'done' : (d.status || 'pending');
|
|
7010
7088
|
var failure = d.failure || null;
|
|
7011
7089
|
if (next !== live.manifest || failure !== live.manifestFailure) { live.manifest = next; live.manifestFailure = failure; rememberManifest(undefined, next); renderSteps(); }
|
|
7012
7090
|
else noteManifestWait();
|
|
7013
|
-
if (!d.terminal) manifestPollTimer = setTimeout(tick, 4000);
|
|
7091
|
+
if (!d.terminal) manifestPollTimer = setTimeout(tick, wizPollDelay(4000));
|
|
7014
7092
|
else manifestPollTimer = null; /* a stale fired id would read as "still polling" and resume a finished poll */
|
|
7015
7093
|
})
|
|
7016
|
-
.catch(function () { if (myGen === manifestGen) manifestPollTimer = setTimeout(tick, 8000); });
|
|
7094
|
+
.catch(function () { if (myGen === manifestGen) manifestPollTimer = setTimeout(tick, wizPollDelay(8000)); });
|
|
7017
7095
|
}
|
|
7018
7096
|
tick();
|
|
7019
7097
|
}
|
|
@@ -7204,6 +7282,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7204
7282
|
function tick() {
|
|
7205
7283
|
if (myGen !== watchGen) return;
|
|
7206
7284
|
if (!wizPollActive()) { watchPending = true; stopPolling(); return; }
|
|
7285
|
+
progressKick = tick;
|
|
7207
7286
|
ticks += 1;
|
|
7208
7287
|
if (ticks > 400) {
|
|
7209
7288
|
/* ~33 min: this tab stops watching, but the standup keeps going — hand
|
|
@@ -7249,6 +7328,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7249
7328
|
if (seedChanged) { rememberManifest(undefined, live.manifest, live.appSlug || undefined); renderSteps(); }
|
|
7250
7329
|
else noteManifestWait();
|
|
7251
7330
|
}
|
|
7331
|
+
/* the server-verified checklist rides the same read (task 1004649) */
|
|
7332
|
+
if (data.github_setup && !manifestPollTimer && seedSetup(data.github_setup.checklist, data.github_setup.install_url)) renderSteps();
|
|
7252
7333
|
/* the checklist reflects live state (task 1002694): a status transition —
|
|
7253
7334
|
e.g. the scaffold landing — re-renders the steps (change-only, so the
|
|
7254
7335
|
5s tick never clobbers an open dropdown or mid-flight button) */
|
|
@@ -7258,7 +7339,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7258
7339
|
var terminal = renderProgress(inst);
|
|
7259
7340
|
/* a GitHub leg still in flight keeps this read alive even once the
|
|
7260
7341
|
project is up — after a reload it is the ONLY thing watching it */
|
|
7261
|
-
if (!terminal || manifestInFlight()) pollTimer = setTimeout(tick, 5000);
|
|
7342
|
+
if (!terminal || manifestInFlight() || setupOpen()) pollTimer = setTimeout(tick, wizPollDelay(5000));
|
|
7262
7343
|
else stopPolling();
|
|
7263
7344
|
})
|
|
7264
7345
|
.catch(function (err) {
|
|
@@ -7271,7 +7352,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7271
7352
|
return;
|
|
7272
7353
|
}
|
|
7273
7354
|
/* transient (network / 5xx): keep the last view, try again a bit later */
|
|
7274
|
-
pollTimer = setTimeout(tick, 10000);
|
|
7355
|
+
pollTimer = setTimeout(tick, wizPollDelay(10000));
|
|
7275
7356
|
});
|
|
7276
7357
|
}
|
|
7277
7358
|
tick();
|
|
@@ -7288,7 +7369,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7288
7369
|
instanceId = null;
|
|
7289
7370
|
watchPending = false; manifestPending = false; manifestState = null;
|
|
7290
7371
|
manifestStartedAt = 0; manifestStale = false;
|
|
7291
|
-
live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null };
|
|
7372
|
+
live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null };
|
|
7292
7373
|
lastStepsHtml = '';
|
|
7293
7374
|
lastProgHtml = '';
|
|
7294
7375
|
hopReset();
|
|
@@ -7320,6 +7401,11 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7320
7401
|
resetForNewProject();
|
|
7321
7402
|
/* the deliberate goodbye: the record goes too, so nothing offers to resume
|
|
7322
7403
|
the project the founder has just said they are finished watching */
|
|
7404
|
+
try {
|
|
7405
|
+
var gone = createdRecord();
|
|
7406
|
+
if (gone && gone.id) sessionStorage.setItem(DECLINED_KEY, String(gone.id));
|
|
7407
|
+
} catch (e) {}
|
|
7408
|
+
serverRecord = null;
|
|
7323
7409
|
try { sessionStorage.removeItem(CREATED_KEY); } catch (e) {}
|
|
7324
7410
|
paintResumeOffer(null);
|
|
7325
7411
|
show(1, true);
|
|
@@ -7722,7 +7808,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7722
7808
|
var list = $('humanSteps'), st = $('ghState');
|
|
7723
7809
|
if (!list || !st) return;
|
|
7724
7810
|
if (state.step !== 2) { st.textContent = 'opens once your project exists'; return; }
|
|
7725
|
-
|
|
7811
|
+
/* the optional step (command-line sign-in) never holds "connected" back */
|
|
7812
|
+
var items = Array.prototype.slice.call(list.children).filter(function (li) { return !li.classList.contains('soptional'); });
|
|
7726
7813
|
var done = items.filter(function (li) { return li.classList.contains('sdone'); }).length;
|
|
7727
7814
|
st.textContent = items.length && done === items.length ? 'connected ✓'
|
|
7728
7815
|
: manifestInFlight() ? 'waiting on GitHub…'
|
|
@@ -7762,8 +7849,11 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7762
7849
|
})();
|
|
7763
7850
|
|
|
7764
7851
|
/* ── the hub's three doors ── */
|
|
7852
|
+
/* the server's resume answer (task 1004650), kept in memory too: a browser whose
|
|
7853
|
+
storage throws still has the project it was handed */
|
|
7854
|
+
var serverRecord = null;
|
|
7765
7855
|
function createdRecord() {
|
|
7766
|
-
try { return JSON.parse(sessionStorage.getItem(CREATED_KEY) || 'null'); } catch (e) { return
|
|
7856
|
+
try { return JSON.parse(sessionStorage.getItem(CREATED_KEY) || 'null') || serverRecord; } catch (e) { return serverRecord; }
|
|
7767
7857
|
}
|
|
7768
7858
|
/* The "still watching …" offer above step 1 (task 1003503). Its markup is baked
|
|
7769
7859
|
into the panel, so this only names the project and shows or hides the line —
|
|
@@ -7828,6 +7918,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7828
7918
|
/* restore the opt-out too — without it resolvedDomain() re-derives the
|
|
7829
7919
|
assigned address and the done panel promises one that doesn't exist */
|
|
7830
7920
|
state.noAddress = !!created.noaddr;
|
|
7921
|
+
/* a record the server handed over (another device, a fresh tab) carries the repo
|
|
7922
|
+
the project is on, so the install step names it (task 1004650) */
|
|
7923
|
+
if (created.repo_ref && !state.repo) {
|
|
7924
|
+
var rr = String(created.repo_ref).split('/');
|
|
7925
|
+
if (rr.length === 2) { state.owner = rr[0]; state.repo = rr[1]; }
|
|
7926
|
+
}
|
|
7831
7927
|
/* a hand-back from the callback page names the attempt GitHub just approved */
|
|
7832
7928
|
if (handbackState) {
|
|
7833
7929
|
created.manifest_state = handbackState;
|
|
@@ -7850,9 +7946,54 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7850
7946
|
identity settles it shows the draft step optimistically (the page stays
|
|
7851
7947
|
usable while /me is in flight — the boot's own rule); onAuth() re-enters
|
|
7852
7948
|
once the answer lands. Focus only moves on a deliberate nav (the flag). */
|
|
7949
|
+
/* askServerResume(focus) — which project is this founder connecting, by the SERVER's
|
|
7950
|
+
view, when this tab cannot say (task 1004650)? Asked once per page load. A hand-back
|
|
7951
|
+
names its project (handbackProject, from the return route): it always lands on that
|
|
7952
|
+
project's part 03, and only its owner gets an answer. With no name, the server picks
|
|
7953
|
+
the founder's newest project whose GitHub is not connected yet: an arrival lands on
|
|
7954
|
+
it unless something is being typed, and a deliberate "New project" only offers it on
|
|
7955
|
+
the resume line. Nothing found → the page as it was. */
|
|
7956
|
+
var resumeAsked = false;
|
|
7957
|
+
function recordFromServer(p) {
|
|
7958
|
+
return { id: p.id, slug: p.slug || '', name: p.slug || '', domain: p.domain || '', noaddr: !p.domain,
|
|
7959
|
+
mode: p.onboard_mode === 'adopt' ? 'adopt' : 'greenfield', repo_ref: p.repo || null, at: Date.now(), from_server: true };
|
|
7960
|
+
}
|
|
7961
|
+
function askServerResume(focus) {
|
|
7962
|
+
resumeAsked = true;
|
|
7963
|
+
var named = handbackProject;
|
|
7964
|
+
handbackProject = null;
|
|
7965
|
+
return fetch(API + '/provisioning/github-app/resume' + (named ? '?id=' + encodeURIComponent(named) : ''), { credentials: 'same-origin' })
|
|
7966
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
7967
|
+
.catch(function () { return null; })
|
|
7968
|
+
.then(function (d) {
|
|
7969
|
+
var p = d && d.project;
|
|
7970
|
+
var had = createdRecord();
|
|
7971
|
+
if (!p || !p.id || (!named && had && had.id)) { if (named) enter(focus); return; }
|
|
7972
|
+
var declined = null;
|
|
7973
|
+
try { declined = sessionStorage.getItem(DECLINED_KEY); } catch (e) {}
|
|
7974
|
+
if (!named && declined === String(p.id)) return;
|
|
7975
|
+
var rec = recordFromServer(p);
|
|
7976
|
+
/* the same project this tab already knew keeps what it knew (a demo's teammates) */
|
|
7977
|
+
if (had && String(had.id) === String(p.id)) rec = Object.assign({}, had, rec, { name: had.name || rec.name });
|
|
7978
|
+
else if (had && had.id) resetForNewProject(); /* a hand-back for a different project */
|
|
7979
|
+
serverRecord = rec;
|
|
7980
|
+
try { sessionStorage.setItem(CREATED_KEY, JSON.stringify(rec)); } catch (e) {}
|
|
7981
|
+
if (currentView !== 'new') return;
|
|
7982
|
+
if (named || (!focus && !String(state.name || '').trim())) { resumeIntent = true; enterCreated(rec, false); }
|
|
7983
|
+
else paintResumeOffer(rec);
|
|
7984
|
+
});
|
|
7985
|
+
}
|
|
7986
|
+
|
|
7853
7987
|
function enter(focus) {
|
|
7854
7988
|
if (authKnown && !me) { show(0, !!focus); return; }
|
|
7855
7989
|
var created = authKnown ? createdRecord() : null;
|
|
7990
|
+
/* this tab cannot say which project — ask the server (task 1004650). A named
|
|
7991
|
+
hand-back waits for that answer instead of painting another project first. */
|
|
7992
|
+
if (authKnown && !resumeAsked && (handbackProject || !(created && created.id))) {
|
|
7993
|
+
var waitForIt = !!handbackProject;
|
|
7994
|
+
askServerResume(!!focus);
|
|
7995
|
+
if (waitForIt) return;
|
|
7996
|
+
}
|
|
7856
7997
|
/* A draft on the page (step 1) beside a stored create is a SECOND project being
|
|
7857
7998
|
typed. A reload or a GitHub round-trip (repo access) is that same visit
|
|
7858
7999
|
continuing, so the draft keeps its place and the earlier project stays one
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.95",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.95",
|
|
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.21.
|
|
3
|
+
"version": "1.21.95",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -9388,5 +9388,17 @@
|
|
|
9388
9388
|
"id": "1004657",
|
|
9389
9389
|
"text": "When a project votes that a part of its government is decided by a group, that vote now holds on every page, not only the People page. Before, someone with the right permission could still switch unattended building on, sign o"
|
|
9390
9390
|
}
|
|
9391
|
+
],
|
|
9392
|
+
"1.21.94": [
|
|
9393
|
+
{
|
|
9394
|
+
"id": "1004649",
|
|
9395
|
+
"text": "Connecting GitHub to a new project is now one trip. The founder clicks \"Connect GitHub\", approves the project's app on GitHub, and GitHub carries straight on to installing it on their repo (already picked when the repo is publ"
|
|
9396
|
+
}
|
|
9397
|
+
],
|
|
9398
|
+
"1.21.95": [
|
|
9399
|
+
{
|
|
9400
|
+
"id": "1004650",
|
|
9401
|
+
"text": "Connecting GitHub no longer depends on staying in one browser tab. After you approve on GitHub, even on your phone or in another window, you land back on your own project's GitHub checklist. Our server remembers which project"
|
|
9402
|
+
}
|
|
9391
9403
|
]
|
|
9392
9404
|
}
|
|
@@ -172,12 +172,14 @@ function main() {
|
|
|
172
172
|
cp.execSync(`systemctl restart ${slug}`, { stdio: 'inherit' });
|
|
173
173
|
console.log(`✓ restarted ${slug}`);
|
|
174
174
|
|
|
175
|
-
// (3) VERIFY — probe
|
|
175
|
+
// (3) VERIFY — probe the JSON health answer for auth_configured (retry briefly for
|
|
176
|
+
// restart). /api/bongos/healthz, not the root /healthz, which answers a plain "ok"
|
|
177
|
+
// that never parses (task 1004577).
|
|
176
178
|
if (!port) { console.error('✖ no PORT in web.env and no --port given — cannot probe /healthz'); process.exit(1); }
|
|
177
179
|
let probe = { authConfigured: false };
|
|
178
180
|
for (let i = 0; i < 10; i++) {
|
|
179
181
|
try {
|
|
180
|
-
const out = cp.execSync(`curl -sS --max-time 5 http://127.0.0.1:${port}/healthz`, { stdio: ['ignore', 'pipe', 'pipe'] }).toString();
|
|
182
|
+
const out = cp.execSync(`curl -sS --max-time 5 http://127.0.0.1:${port}/api/bongos/healthz`, { stdio: ['ignore', 'pipe', 'pipe'] }).toString();
|
|
181
183
|
probe = parseHealthzAuth(out);
|
|
182
184
|
if (probe.ok || probe.authConfigured) break;
|
|
183
185
|
} catch { /* not up yet */ }
|