@bongos/core 1.19.980 → 1.19.982
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 +47 -32
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0176-private-repo-deploy-keys.md +5 -4
- package/docs/api/openapi.json +87 -4
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +204 -208
- package/docs/copy-registry.json +268 -304
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/blockers-lib.js +14 -19
- package/modules/hall-ui/public/blockers.js +7 -5
- package/modules/hall-ui/public/goal-inbox.js +2 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/routes/repo-private.js +82 -0
- package/modules/public-landing/public/projects.html +129 -21
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/routes/github.js +10 -9
- package/src/module-api.js +1 -1
- package/tests/blockers_lib.mjs +22 -14
- package/tests/github_repo_visibility.mjs +9 -6
- package/tests/projects_hub.mjs +18 -24
- package/tests/projects_hub_pre_uat.mjs +5 -4
- package/tests/projects_hub_repo_private.mjs +238 -0
- package/tests/provisioning_repo_private_route.mjs +224 -0
- package/tests/public_landing_projects.mjs +7 -13
|
@@ -2434,5 +2434,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2434
2434
|
landed since 1.19.978 with no explicit bump. run 35887876013. (task 1002620)
|
|
2435
2435
|
1.19.980 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2436
2436
|
landed since 1.19.979 with no explicit bump. run 35889261972. (task 1002620)
|
|
2437
|
+
1.19.981 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2438
|
+
landed since 1.19.980 with no explicit bump. run 35891635199. (task 1002620)
|
|
2439
|
+
1.19.982 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2440
|
+
landed since 1.19.981 with no explicit bump. run 35894422097. (task 1002620)
|
|
2437
2441
|
---------------------------------------------------------------------------
|
|
2438
2442
|
```
|
|
@@ -28,25 +28,12 @@
|
|
|
28
28
|
}(typeof self !== 'undefined' ? self : this, function () {
|
|
29
29
|
'use strict';
|
|
30
30
|
|
|
31
|
-
function esc(s) {
|
|
32
|
-
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
33
|
-
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function fmtDate(iso) {
|
|
38
|
-
if (!iso) return '';
|
|
39
|
-
var d = new Date(iso);
|
|
40
|
-
if (isNaN(d.getTime())) return '';
|
|
41
|
-
return d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
31
|
// A blocker's body_md is the owner's own words, quoted the way goal-inbox.js's
|
|
45
32
|
// invitation/join rows already quote a note (the SAME convention, not a new
|
|
46
33
|
// one). It can run to a paragraph, and the row is a summary, so it is clipped —
|
|
47
34
|
// CSS line-clamp handles visual overflow on both surfaces, this just keeps one
|
|
48
35
|
// enormous blocker from making the row's own text node absurd.
|
|
49
|
-
function noteHtml(bodyMd, noteClass) {
|
|
36
|
+
function noteHtml(bodyMd, noteClass, esc) {
|
|
50
37
|
var t = String(bodyMd || '').trim();
|
|
51
38
|
if (!t) return '';
|
|
52
39
|
var clipped = t.length > 240 ? t.slice(0, 239).replace(/\s+$/, '') + '…' : t;
|
|
@@ -55,8 +42,9 @@
|
|
|
55
42
|
|
|
56
43
|
// `taskHref(id)` is the caller's own link builder (window.OTBTaskDetail.href on
|
|
57
44
|
// a live page) — kept OUT of this pure module the way hall-kit.js stays
|
|
58
|
-
// date-agnostic: the lib never learns what a task URL looks like.
|
|
59
|
-
|
|
45
|
+
// date-agnostic: the lib never learns what a task URL looks like. `esc` is the
|
|
46
|
+
// caller's too (OTBKit.esc): the lib owns no formatter of its own (task 1003984).
|
|
47
|
+
function blockingLine(b, taskHref, esc) {
|
|
60
48
|
var ids = Array.isArray(b.blocking_task_ids) ? b.blocking_task_ids : [];
|
|
61
49
|
if (!ids.length) return 'Blocking nothing yet';
|
|
62
50
|
var links = ids.map(function (id) {
|
|
@@ -76,11 +64,18 @@
|
|
|
76
64
|
// the "reachable only by typing the URL" bug this task
|
|
77
65
|
// fixes is exactly that overlay having no door INTO it).
|
|
78
66
|
// taskHref(id) — see blockingLine above.
|
|
67
|
+
// esc(s), fmtDate(iso) — REQUIRED; callers pass OTBKit.esc / OTBKit.fmtAbsDate
|
|
68
|
+
// so the hall keeps ONE definition of each (task 1003984).
|
|
79
69
|
// Returns { titleHtml, noteHtml, factsHtml } — fragments, not a wrapped row;
|
|
80
70
|
// each caller nests them in its OWN <li> (goal-inbox.js's .ginbox__item,
|
|
81
71
|
// blockers.js's .ov-row), the goalRenderFacts idiom.
|
|
82
72
|
function blockerFacts(b, opts) {
|
|
83
73
|
opts = opts || {};
|
|
74
|
+
var esc = opts.esc;
|
|
75
|
+
var fmtDate = opts.fmtDate;
|
|
76
|
+
if (typeof esc !== 'function' || typeof fmtDate !== 'function') {
|
|
77
|
+
throw new TypeError('blockerFacts: opts.esc and opts.fmtDate are required (pass OTBKit.esc / OTBKit.fmtAbsDate)');
|
|
78
|
+
}
|
|
84
79
|
var titleClass = opts.titleClass || 'ov-row__title';
|
|
85
80
|
var noteClass = opts.noteClass || 'ov-row__sub';
|
|
86
81
|
var whenClass = opts.whenClass || 'ov-row__when';
|
|
@@ -96,15 +91,15 @@
|
|
|
96
91
|
// join multiple facts into a single .ov-row__when span the same way) rather
|
|
97
92
|
// than several sibling spans, which need a flex-gapped parent to space
|
|
98
93
|
// themselves and the attention-inbox context (.ginbox__sub) is not one.
|
|
99
|
-
var facts = 'Filed ' + esc(fmtDate(b.created_at)) + ' · ' + blockingLine(b, taskHref);
|
|
94
|
+
var facts = 'Filed ' + esc(fmtDate(b.created_at)) + ' · ' + blockingLine(b, taskHref, esc);
|
|
100
95
|
var factsHtml = '<span class="' + esc(whenClass) + '">' + facts + '</span>';
|
|
101
96
|
|
|
102
97
|
return {
|
|
103
98
|
titleHtml: titleHtml,
|
|
104
|
-
noteHtml: noteHtml(b.body_md, noteClass),
|
|
99
|
+
noteHtml: noteHtml(b.body_md, noteClass, esc),
|
|
105
100
|
factsHtml: factsHtml,
|
|
106
101
|
};
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
return {
|
|
104
|
+
return { blockingLine: blockingLine, blockerFacts: blockerFacts };
|
|
110
105
|
}));
|
|
@@ -120,12 +120,14 @@
|
|
|
120
120
|
whenClass: 'ov-row__when',
|
|
121
121
|
blockerHref: blockerHref,
|
|
122
122
|
taskHref: taskHref,
|
|
123
|
+
esc: kit.esc,
|
|
124
|
+
fmtDate: kit.fmtAbsDate,
|
|
123
125
|
});
|
|
124
126
|
var right = '';
|
|
125
127
|
if (isReviewer()) {
|
|
126
128
|
var id = String(b.id);
|
|
127
129
|
if (openRow && String(openRow.id) === id && openRow.mode === 'resolve') {
|
|
128
|
-
right = '<div class="ov-row__right"><form class="td-form" data-resolve-form="' +
|
|
130
|
+
right = '<div class="ov-row__right"><form class="td-form" data-resolve-form="' + kit.esc(id) + '">'
|
|
129
131
|
+ '<label class="td-field td-field--wide"><span>Resolution note (optional)</span>'
|
|
130
132
|
+ '<input type="text" name="note" maxlength="1000" placeholder="what got un-stuck"></label>'
|
|
131
133
|
+ '<div class="td-actions">'
|
|
@@ -133,7 +135,7 @@
|
|
|
133
135
|
+ '<button type="button" class="td-btn" data-row-cancel>Cancel</button>'
|
|
134
136
|
+ '</div><p class="td-action-note" data-err></p></form></div>';
|
|
135
137
|
} else if (openRow && String(openRow.id) === id && openRow.mode === 'link') {
|
|
136
|
-
right = '<div class="ov-row__right"><form class="td-form" data-link-form="' +
|
|
138
|
+
right = '<div class="ov-row__right"><form class="td-form" data-link-form="' + kit.esc(id) + '">'
|
|
137
139
|
+ '<label class="td-field td-field--wide"><span>Task #</span>'
|
|
138
140
|
+ '<input type="number" name="taskId" min="1" required placeholder="e.g. 1002980"></label>'
|
|
139
141
|
+ '<div class="td-actions">'
|
|
@@ -142,8 +144,8 @@
|
|
|
142
144
|
+ '</div><p class="td-action-note" data-err></p></form></div>';
|
|
143
145
|
} else {
|
|
144
146
|
right = '<div class="ov-row__right"><div class="ov-row__actions">'
|
|
145
|
-
+ '<button type="button" class="btn-ghost" data-resolve-open="' +
|
|
146
|
-
+ '<button type="button" class="btn-ghost" data-link-open="' +
|
|
147
|
+
+ '<button type="button" class="btn-ghost" data-resolve-open="' + kit.esc(id) + '">Resolve</button>'
|
|
148
|
+
+ '<button type="button" class="btn-ghost" data-link-open="' + kit.esc(id) + '">Link a task</button>'
|
|
147
149
|
+ '</div></div>';
|
|
148
150
|
}
|
|
149
151
|
}
|
|
@@ -154,7 +156,7 @@
|
|
|
154
156
|
// three-column grid (auto minmax(0,1fr) auto) would misread the actions
|
|
155
157
|
// column as the row's middle content and pin it beside the text on a
|
|
156
158
|
// phone instead of dropping it beneath.
|
|
157
|
-
return '<li class="ov-row" data-id="' +
|
|
159
|
+
return '<li class="ov-row" data-id="' + kit.esc(String(b.id)) + '">'
|
|
158
160
|
+ '<div class="ov-row__main">'
|
|
159
161
|
+ facts.titleHtml
|
|
160
162
|
+ facts.noteHtml
|
|
@@ -495,6 +495,8 @@
|
|
|
495
495
|
whenClass: 'ginbox__sub',
|
|
496
496
|
// The SAME overlay task-detail.js has rendered since task 1003066's
|
|
497
497
|
// predecessor — this slice is what gives it its first door in.
|
|
498
|
+
esc: window.OTBKit.esc,
|
|
499
|
+
fmtDate: window.OTBKit.fmtAbsDate,
|
|
498
500
|
blockerHref: (id) => `#/blocker/${encodeURIComponent(id)}`,
|
|
499
501
|
taskHref: (id) => (window.OTBTaskDetail ? window.OTBTaskDetail.href(id) : `/task/${encodeURIComponent(id)}`),
|
|
500
502
|
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// modules/provisioning/routes/repo-private.js — POST /provisioning/instances/:id/repo-private
|
|
2
|
+
// (split (c) of task 1003078; ADR 0176).
|
|
3
|
+
//
|
|
4
|
+
// WHY. A project stood up while its repository was public reads its code with the shared key,
|
|
5
|
+
// which cannot read a private repository, so a plain flip would break its next deploy. This
|
|
6
|
+
// route only ENQUEUES the `repo-private` intent; the runner (scripts/gds/provision-repo-private.js)
|
|
7
|
+
// wires the project's own read-only deploy key FIRST and flips SECOND. Going public stays the
|
|
8
|
+
// direct core call (POST /github/repo/visibility) — it needs no wiring.
|
|
9
|
+
//
|
|
10
|
+
// Its own routes file (declared in module.json) because routes/provisioning.js sits at the
|
|
11
|
+
// size ratchet — the routes/disconnect.js precedent.
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const express = require('express');
|
|
15
|
+
const api = require('../../../src/module-api');
|
|
16
|
+
const { pool, validateOrRespond, parseId } = api;
|
|
17
|
+
const provisioning = require('../provisioning');
|
|
18
|
+
const repoPrivate = require('../repo-private');
|
|
19
|
+
|
|
20
|
+
const log = api.logger('provisioning');
|
|
21
|
+
|
|
22
|
+
// What the runner leg would refuse, answered here so the owner hears it at once instead of
|
|
23
|
+
// as an error note once its retries are spent. The runner keeps its own checks — the row can
|
|
24
|
+
// change between this read and the drain.
|
|
25
|
+
function unavailableReason(inst) {
|
|
26
|
+
if (inst.hosting_shape === 'byo-host') {
|
|
27
|
+
return 'This project runs on your own host, so the platform never reads its code — you can make the repository private on GitHub directly.';
|
|
28
|
+
}
|
|
29
|
+
if (inst.hosting_shape !== 'standalone') {
|
|
30
|
+
return 'Only a project on a Cloud host can have its repository made private from here.';
|
|
31
|
+
}
|
|
32
|
+
if (inst.status !== 'active') {
|
|
33
|
+
return 'This project isn’t running right now, so its repository can’t be made private from here. Try again once it’s live.';
|
|
34
|
+
}
|
|
35
|
+
if (!inst.target_ref) return 'This project has no GitHub repository linked, so there is nothing to make private.';
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = function buildRepoPrivateRouter() {
|
|
40
|
+
const router = express.Router();
|
|
41
|
+
|
|
42
|
+
// POST /provisioning/instances/:id/repo-private — rank: any authenticated builder, and
|
|
43
|
+
// OWNER ONLY: no archon override, because the runner flips with the owner's stored GitHub
|
|
44
|
+
// token whoever asked (owner decision, task 1004194). 404 for everyone else, so another
|
|
45
|
+
// owner's id is never confirmed. Takes no body. The reply is a receipt — the repository is
|
|
46
|
+
// still public when it is sent; the manage page re-reads GitHub once the intent settles.
|
|
47
|
+
router.post('/provisioning/instances/:id/repo-private', api.requireBuilder, async (req, res) => {
|
|
48
|
+
const id = parseId(req, res, { positiveInt: true }); // 400 bad_id + null on failure; positiveInt so id 0 is answered, not dropped
|
|
49
|
+
if (!id) return;
|
|
50
|
+
if (validateOrRespond(req, res, {})) return;
|
|
51
|
+
try {
|
|
52
|
+
const inst = await provisioning.getInstanceById(pool, id);
|
|
53
|
+
if (!inst || String(inst.owner_builder_id) !== String(req.builder.id)) return res.fail('instance_not_found', 404);
|
|
54
|
+
const why = unavailableReason(inst);
|
|
55
|
+
if (why) {
|
|
56
|
+
return res.fail('repo_private_unavailable', { status: 409, message: why,
|
|
57
|
+
details: { status: inst.status, hosting_shape: inst.hosting_shape } });
|
|
58
|
+
}
|
|
59
|
+
const r = await repoPrivate.enqueueRepoPrivate(pool, id, 'api:self');
|
|
60
|
+
if (r.conflict) {
|
|
61
|
+
return res.fail('intent_conflict', { status: 409,
|
|
62
|
+
message: `A '${r.intent.action}' run is currently ${r.intent.state} for this project — making the repository private waits for it to finish. Try again once it has.`,
|
|
63
|
+
details: { action: r.intent.action, state: r.intent.state } });
|
|
64
|
+
}
|
|
65
|
+
if (r.created) {
|
|
66
|
+
await provisioning.recordEvent(pool, {
|
|
67
|
+
instanceId: id, ownerBuilderId: inst.owner_builder_id, event: 'repo-private',
|
|
68
|
+
detail: 'making the repository private requested by owner', actor: 'api:self',
|
|
69
|
+
}).catch(() => {});
|
|
70
|
+
}
|
|
71
|
+
res.json({ ok: true, queued: r.created, action: 'repo-private', status: inst.status,
|
|
72
|
+
message: r.created
|
|
73
|
+
? 'Queued. The platform first gives this project its own read-only deploy key, then makes the repository private. This page updates when it’s done.'
|
|
74
|
+
: 'Making the repository private is already under way for this project.' });
|
|
75
|
+
} catch (err) {
|
|
76
|
+
log.error('[provisioning] POST /provisioning/instances/:id/repo-private', err);
|
|
77
|
+
res.fail('repo_private_failed', { status: 500, message: 'internal error' });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return router;
|
|
82
|
+
};
|
|
@@ -762,20 +762,16 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
762
762
|
</div>
|
|
763
763
|
|
|
764
764
|
<!-- live since task 1003045: reads the repo's real visibility from GitHub
|
|
765
|
-
with the owner's own repository credential.
|
|
766
|
-
|
|
767
|
-
deploy
|
|
768
|
-
(walk finding N4). -->
|
|
765
|
+
with the owner's own repository credential. Going public is a direct
|
|
766
|
+
call; going private (task 1003078) is QUEUED, because the project's own
|
|
767
|
+
deploy key must be wired before the flip or its next deploy breaks
|
|
768
|
+
(walk finding N4, ADR 0176). -->
|
|
769
769
|
<div class="card mSec" id="mRepoVisibility">
|
|
770
770
|
<h2>Repository visibility</h2>
|
|
771
771
|
<div id="mRepoVis"><p class="finePrint">Checking the repository on GitHub…</p></div>
|
|
772
|
-
<!-- the limitation itself, one click away (grade follow-up task 1003077):
|
|
773
|
-
the refusal above states it; this fold explains it and links the
|
|
774
|
-
tracked fix. The hall link is apex-only — a child instance keeps the
|
|
775
|
-
explanation, not another platform's task tracker. -->
|
|
776
772
|
<details class="finePrint" id="mRepoVisWhy">
|
|
777
|
-
<summary>
|
|
778
|
-
<p>
|
|
773
|
+
<summary>What making it private does</summary>
|
|
774
|
+
<p>The platform first gives this project its own read-only deploy key and switches its deploys over to it, and only then makes the repository private — in that order, so no deploy ever tries to read a private repository with a key that can’t. The change is made with your connected GitHub account, so that account needs admin rights on the repository. It works for a project on a Cloud host; a project on your own host is yours to change on GitHub directly. You can make it public again from here at any time.</p>
|
|
779
775
|
</details>
|
|
780
776
|
</div>
|
|
781
777
|
|
|
@@ -5149,7 +5145,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5149
5145
|
teardown: { names: ['Teardown queued', 'Winding down', 'Torn down'], cls: 'flowDown' },
|
|
5150
5146
|
address: { names: ['Address queued', 'Wiring the address', 'Answering there'], cls: '' },
|
|
5151
5147
|
settings: { names: ['Change saved', 'Restarting to apply it', 'Applied'], cls: '' },
|
|
5152
|
-
restart: { names: ['Restart queued', 'Restarting', 'Answering again'], cls: '' }
|
|
5148
|
+
restart: { names: ['Restart queued', 'Restarting', 'Answering again'], cls: '' },
|
|
5149
|
+
repo: { names: ['Change queued', 'Setting up its deploy key', 'Private'], cls: '' }
|
|
5153
5150
|
};
|
|
5154
5151
|
function stripMarkup(flow, stageNow, stagesDone, note, extraCls) {
|
|
5155
5152
|
var f = FLOWS[flow];
|
|
@@ -5191,6 +5188,10 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5191
5188
|
if (a === 'restart') return inst.open_intent.state === 'running'
|
|
5192
5189
|
? stripMarkup('restart', 1, 1, 'Restarting the project — it goes down and comes back up.')
|
|
5193
5190
|
: stripMarkup('restart', 0, 0, 'A restart is queued — this card updates when it starts.');
|
|
5191
|
+
/* task 1003078: the deploy key is wired BEFORE the flip — the strip says which half is running */
|
|
5192
|
+
if (a === 'repo-private') return inst.open_intent.state === 'running'
|
|
5193
|
+
? stripMarkup('repo', 1, 1, 'Making the repository private — setting up the project’s own deploy key first.')
|
|
5194
|
+
: stripMarkup('repo', 0, 0, 'Making the repository private is queued — this card updates when it starts.');
|
|
5194
5195
|
if (a === 'provision') return stripMarkup(s === 'torn_down' ? 'revive' : 'standup', 0, 0, (s === 'torn_down' ? 'A revive is queued' : 'A standup is queued') + ' — this card updates when it starts.');
|
|
5195
5196
|
return stripMarkup('standup', 2, 2, 'A ' + esc(a) + ' run is queued — this card updates when it lands.');
|
|
5196
5197
|
}
|
|
@@ -5215,6 +5216,10 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5215
5216
|
var n = String(note || '');
|
|
5216
5217
|
if (/^repository not found:/i.test(n)) return 'We couldn’t find the project’s repository on GitHub even with the access you granted — it may have been renamed or deleted, or the access was lost.';
|
|
5217
5218
|
if (/^repository access is not admin:/i.test(n)) return 'Your GitHub access can read the repository but can’t manage its deploy keys — a repository admin needs to raise your access (or make the repository public).';
|
|
5219
|
+
/* the repo-private runner leg's own sentences (task 1003078) — the project is live */
|
|
5220
|
+
if (/made private[\s\S]*credential/i.test(n)) return 'Making the repository private didn’t finish — the project’s GitHub access needs reconnecting first. Its repository is still public.';
|
|
5221
|
+
if (/is still public|made private|left public/i.test(n)) return 'Making the repository private didn’t finish — it’s still public.';
|
|
5222
|
+
if (/^deploy-key setup failed/i.test(n)) return 'Setting up the project’s own read-only deploy key on GitHub didn’t finish.';
|
|
5218
5223
|
if (/repository not found/i.test(n)) return 'We couldn’t push the starter code to the repository — it wasn’t found on GitHub. A private repository looks “not found” too — if yours is private, sign in again to re-grant repository access, then retry.';
|
|
5219
5224
|
if (/mkdir|no such file or directory|read-only file system/i.test(n)) return 'The server couldn’t prepare a workspace for the project.';
|
|
5220
5225
|
if (/(push|clone|github).*permission|permission.*(push|clone|repo)/i.test(n)) return 'We couldn’t reach the repository — the account that set it up doesn’t have permission.';
|
|
@@ -5617,6 +5622,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5617
5622
|
only, so a queued settings push shows up on the tick without a reload */
|
|
5618
5623
|
renderVisState(inst);
|
|
5619
5624
|
renderJoinState(inst);
|
|
5625
|
+
renderRepoVisState(inst);
|
|
5620
5626
|
/* its planet (task 1003347) reads off the row — fetch-free too; a pick in
|
|
5621
5627
|
progress survives the tick because only a different project re-renders it */
|
|
5622
5628
|
renderPick(inst);
|
|
@@ -5853,6 +5859,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5853
5859
|
$('mChip').innerHTML = '';
|
|
5854
5860
|
$('mOrb').innerHTML = '';
|
|
5855
5861
|
}
|
|
5862
|
+
if (!keepRepo) { mRepoPrivSeen = false; mRepoPrivState = ''; }
|
|
5856
5863
|
var asked = manageId; /* a stale answer must never paint a newer project */
|
|
5857
5864
|
fetch(API + '/provisioning/instances/' + encodeURIComponent(manageId), { credentials: 'same-origin' })
|
|
5858
5865
|
.then(function (res) {
|
|
@@ -5922,14 +5929,16 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5922
5929
|
setInterval(manageChipTick, REFRESH_MS);
|
|
5923
5930
|
|
|
5924
5931
|
/* ── repo visibility (task 1003045): read live from GitHub with the owner's
|
|
5925
|
-
own repository credential
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
read a private repo (walk finding N4)
|
|
5929
|
-
|
|
5930
|
-
|
|
5932
|
+
own repository credential. Going public is a direct call. Going private
|
|
5933
|
+
(task 1003078) is QUEUED on the provisioning route: the runner wires the
|
|
5934
|
+
project's own deploy key first, because a project stood up public reads
|
|
5935
|
+
with the shared key, which cannot read a private repo (walk finding N4).
|
|
5936
|
+
Every claim below is what GitHub answered for THIS account — never a
|
|
5937
|
+
cached or guessed state; the queue's receipt is not a new visibility. ── */
|
|
5931
5938
|
|
|
5932
5939
|
var manageRepo = null; /* GitHub's last answer for the manage view's repo */
|
|
5940
|
+
var mRepoPrivSeen = false; /* a make-private run was open — ask GitHub again once it settles */
|
|
5941
|
+
var mRepoPrivState = ''; /* the in-flight line last painted: '', 'pending' or 'running' */
|
|
5933
5942
|
|
|
5934
5943
|
function repoVisReconnect() {
|
|
5935
5944
|
return API + '/auth/web/start?repos=1&return=' +
|
|
@@ -5947,14 +5956,67 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5947
5956
|
'<div class="addr">This repository is <b>private</b> — only accounts it’s shared with can read its code.</div>' +
|
|
5948
5957
|
(snagged
|
|
5949
5958
|
? '<div class="projMsg err">A private repository deploys through the project’s own read-only deploy key, wired at standup. If the standup couldn’t read it, retry it from the card above — or make the repository public.</div>'
|
|
5950
|
-
: '<p class="finePrint">The platform reads it through this project’s own read-only deploy key
|
|
5959
|
+
: '<p class="finePrint">The platform reads it through this project’s own read-only deploy key. Nothing to do here.</p>') +
|
|
5951
5960
|
(repo.admin
|
|
5952
5961
|
? '<div class="actions"><button type="button" class="btn small ghost" data-repo-public>Make it public</button></div>'
|
|
5953
5962
|
: '<p class="finePrint">Your GitHub account can’t change this repository’s settings.</p>'));
|
|
5954
5963
|
} else {
|
|
5955
|
-
repoVisMsg(
|
|
5956
|
-
|
|
5957
|
-
|
|
5964
|
+
repoVisMsg('<div class="addr">This repository is <b>public</b> — anyone can read its code.</div>' +
|
|
5965
|
+
(repoPrivOpen(manageInst)
|
|
5966
|
+
? '<p class="finePrint">' + (manageInst.open_intent.state === 'running'
|
|
5967
|
+
? 'Making it private — the platform is giving this project its own deploy key first.'
|
|
5968
|
+
: 'Making it private is queued — the platform gives this project its own deploy key first. Until then it stays public.') + '</p>'
|
|
5969
|
+
: repoPrivDoor(manageInst, repo)) +
|
|
5970
|
+
/* the make-private receipt's slot, painted WITH the state (the #mVisMsg idiom) —
|
|
5971
|
+
a sibling div would land on this one: every .mSec>div takes the same grid cell */
|
|
5972
|
+
'<div class="projMsg" id="mRepoVisMsg" role="status" aria-live="polite"></div>');
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5976
|
+
/* fetch-free, from open_intent only (renderManage calls it on every repaint) */
|
|
5977
|
+
function repoPrivOpen(inst) {
|
|
5978
|
+
return !!(inst && inst.open_intent && inst.open_intent.action === 'repo-private');
|
|
5979
|
+
}
|
|
5980
|
+
|
|
5981
|
+
/* the one door to private, or the sentence saying why there is none here — the
|
|
5982
|
+
route refuses the same cases; this only keeps a button off a dead end */
|
|
5983
|
+
function repoPrivDoor(inst, repo) {
|
|
5984
|
+
if (!inst) return '';
|
|
5985
|
+
var why = inst.hosting_shape === 'byo-host' ? 'This project runs on your own host, so the platform never reads its code — you can make the repository private on GitHub directly.'
|
|
5986
|
+
: inst.hosting_shape !== 'standalone' ? 'Only a project on a Cloud host can have its repository made private from here.'
|
|
5987
|
+
: inst.status !== 'active' ? 'You can make it private once the project is running.'
|
|
5988
|
+
: !repo.admin ? 'Your GitHub account can’t change this repository’s settings.'
|
|
5989
|
+
: '';
|
|
5990
|
+
if (why) return '<p class="finePrint">' + why + '</p>';
|
|
5991
|
+
return '<p class="finePrint">Making it private gives this project its own deploy key first, so its deploys keep working.</p>' +
|
|
5992
|
+
'<div class="actions"><button type="button" class="btn small ghost" data-repo-private>Make it private</button></div>';
|
|
5993
|
+
}
|
|
5994
|
+
|
|
5995
|
+
function repoVisNote(cls, html) {
|
|
5996
|
+
var box = $('mRepoVisMsg');
|
|
5997
|
+
if (!box) {
|
|
5998
|
+
if (!html) return;
|
|
5999
|
+
$('mRepoVis').insertAdjacentHTML('beforeend', '<div class="projMsg" id="mRepoVisMsg" role="status" aria-live="polite"></div>');
|
|
6000
|
+
box = $('mRepoVisMsg');
|
|
6001
|
+
}
|
|
6002
|
+
box.className = 'projMsg' + (cls ? ' ' + cls : '');
|
|
6003
|
+
box.innerHTML = html;
|
|
6004
|
+
}
|
|
6005
|
+
|
|
6006
|
+
/* the in-flight line is repainted only when the run's state MOVES, so the tick
|
|
6007
|
+
never flickers it; once the run settles, GitHub is asked again — the only
|
|
6008
|
+
source that can say the flip took (the renderVisState contract) */
|
|
6009
|
+
function renderRepoVisState(inst) {
|
|
6010
|
+
var now = repoPrivOpen(inst) ? inst.open_intent.state : '';
|
|
6011
|
+
if (now) mRepoPrivSeen = true;
|
|
6012
|
+
if (now !== mRepoPrivState) {
|
|
6013
|
+
mRepoPrivState = now;
|
|
6014
|
+
if (manageRepo && !manageRepo.private) renderRepoVis(manageRepo);
|
|
6015
|
+
}
|
|
6016
|
+
if (!now && mRepoPrivSeen) {
|
|
6017
|
+
mRepoPrivSeen = false;
|
|
6018
|
+
repoVisNote('', '');
|
|
6019
|
+
loadRepoVisibility(inst);
|
|
5958
6020
|
}
|
|
5959
6021
|
}
|
|
5960
6022
|
|
|
@@ -6034,12 +6096,58 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6034
6096
|
});
|
|
6035
6097
|
}
|
|
6036
6098
|
|
|
6099
|
+
function askRepoPrivate() {
|
|
6100
|
+
var act = document.querySelector('#mRepoVis .actions');
|
|
6101
|
+
if (!act) return;
|
|
6102
|
+
act.innerHTML = '<div class="confirmRow">' +
|
|
6103
|
+
'<span class="plain">Make it private? Only accounts it’s shared with will be able to read the code.</span>' +
|
|
6104
|
+
'<button type="button" class="btn small" data-repo-private-yes>Yes, make it private</button>' +
|
|
6105
|
+
'<button type="button" class="linklike" data-repo-private-no>Keep it public</button></div>';
|
|
6106
|
+
}
|
|
6107
|
+
|
|
6108
|
+
/* the reply is a RECEIPT — the repository is still public when it lands. The
|
|
6109
|
+
in-flight line reads open_intent, so the manage row is re-read, never patched */
|
|
6110
|
+
function doRepoPrivate() {
|
|
6111
|
+
if (!manageInst) return;
|
|
6112
|
+
var btn = document.querySelector('#mRepoVis [data-repo-private-yes]');
|
|
6113
|
+
if (btn) btn.disabled = true;
|
|
6114
|
+
repoVisNote('', 'Queueing…');
|
|
6115
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(manageId) + '/repo-private', {
|
|
6116
|
+
method: 'POST', credentials: 'same-origin'
|
|
6117
|
+
}).then(function (res) {
|
|
6118
|
+
return jsonOrEmpty(res).then(function (data) {
|
|
6119
|
+
if (res.ok) {
|
|
6120
|
+
/* the receipt proves a run is OPEN — never that the repository changed. Record
|
|
6121
|
+
that much, so the 5s watch follows it and a repaint from the stale row cannot
|
|
6122
|
+
drop it; the re-read below swaps in the server's own open_intent */
|
|
6123
|
+
if (!repoPrivOpen(manageInst)) manageInst.open_intent = { action: 'repo-private', state: 'pending' };
|
|
6124
|
+
renderRepoVisState(manageInst);
|
|
6125
|
+
repoVisNote('ok', esc(data.message || 'Queued.'));
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6128
|
+
var e = apiError(data, res.status);
|
|
6129
|
+
if (manageRepo) renderRepoVis(manageRepo);
|
|
6130
|
+
repoVisNote('err', res.status === 401 ? signInAgain()
|
|
6131
|
+
: res.status === 404 ? 'Only the project’s owner can make its repository private.'
|
|
6132
|
+
: esc(e.message || 'The change could not be queued — try again in a moment.'));
|
|
6133
|
+
});
|
|
6134
|
+
}).catch(function () {
|
|
6135
|
+
if (manageRepo) renderRepoVis(manageRepo);
|
|
6136
|
+
repoVisNote('err', 'Couldn’t reach the platform — check your connection and try again.');
|
|
6137
|
+
}).then(function () {
|
|
6138
|
+
loadManage(true, true);
|
|
6139
|
+
});
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6037
6142
|
$('mRepoVis').addEventListener('click', function (ev) {
|
|
6038
6143
|
var t = ev.target.closest && ev.target.closest('button');
|
|
6039
6144
|
if (!t) return;
|
|
6040
6145
|
if (t.hasAttribute('data-repo-public')) return askRepoPublic();
|
|
6041
6146
|
if (t.hasAttribute('data-repo-public-yes')) return doRepoPublic();
|
|
6042
6147
|
if (t.hasAttribute('data-repo-public-no')) { if (manageRepo) renderRepoVis(manageRepo); }
|
|
6148
|
+
if (t.hasAttribute('data-repo-private')) return askRepoPrivate();
|
|
6149
|
+
if (t.hasAttribute('data-repo-private-yes')) return doRepoPrivate();
|
|
6150
|
+
if (t.hasAttribute('data-repo-private-no')) { if (manageRepo) renderRepoVis(manageRepo); }
|
|
6043
6151
|
});
|
|
6044
6152
|
|
|
6045
6153
|
/* ── on the public map (task 1002335, BV1.R16): the publish gate ──────────
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.982",
|
|
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.982",
|
|
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.982",
|
|
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",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/bongos/routes/github.js — GitHub repo list/create for the signed-in user, the
|
|
2
2
|
// server half of the onboarding wizard's repo picker (adopt) + auto-create (fresh),
|
|
3
3
|
// plus the manage page's repo-visibility read/flip (task 1003045 — public only; the
|
|
4
|
-
// private direction is
|
|
4
|
+
// private direction is the provisioning module's queued route, task 1003078).
|
|
5
5
|
// Task 1002099 / ADR 0143.
|
|
6
6
|
//
|
|
7
7
|
// These act on the user's OWN GitHub identity, so they live in core auth alongside
|
|
@@ -127,9 +127,9 @@ module.exports = function buildGithubRouter() {
|
|
|
127
127
|
if (!token) return;
|
|
128
128
|
try {
|
|
129
129
|
const repo = await githubRepos.getUserRepo(token, ref);
|
|
130
|
-
// private_deployable
|
|
131
|
-
//
|
|
132
|
-
res.json({ ok: true, repo, private_deployable:
|
|
130
|
+
// private_deployable: a private repo deploys through the project's own read-only
|
|
131
|
+
// deploy key (ADR 0176), which the manage page's make-private route wires first.
|
|
132
|
+
res.json({ ok: true, repo, private_deployable: true });
|
|
133
133
|
} catch (err) {
|
|
134
134
|
if (err && err.status === 401) {
|
|
135
135
|
await db.deleteGithubToken({ builderId: req.builder.id }).catch(() => {});
|
|
@@ -145,9 +145,10 @@ module.exports = function buildGithubRouter() {
|
|
|
145
145
|
|
|
146
146
|
// rank: any-builder — flips one of the caller's repos PUBLIC, with their own
|
|
147
147
|
// credential. The private direction is REFUSED here, before GitHub is ever
|
|
148
|
-
// called:
|
|
149
|
-
// a private repo (walk finding N4), so
|
|
150
|
-
//
|
|
148
|
+
// called: a project stood up public reads its code with the shared key, which
|
|
149
|
+
// cannot read a private repo (walk finding N4), so a bare flip would break its
|
|
150
|
+
// next deploy. Making it private is POST /provisioning/instances/:id/repo-private
|
|
151
|
+
// (task 1003078), which wires the project's own deploy key before it flips.
|
|
151
152
|
router.post('/github/repo/visibility', auth.requireBuilder, async (req, res) => {
|
|
152
153
|
if (validateOrRespond(req, res, {
|
|
153
154
|
repo: { required: true, type: 'string', maxLength: 140 },
|
|
@@ -158,14 +159,14 @@ module.exports = function buildGithubRouter() {
|
|
|
158
159
|
if (req.body.visibility !== 'public') {
|
|
159
160
|
return res.fail('private_not_deployable', {
|
|
160
161
|
status: 409,
|
|
161
|
-
message: '
|
|
162
|
+
message: 'Make a repository private from its project’s manage page — the platform has to give the project its own deploy key first, or its next deploy would break.',
|
|
162
163
|
});
|
|
163
164
|
}
|
|
164
165
|
const token = await resolveRepoToken(req, res);
|
|
165
166
|
if (!token) return;
|
|
166
167
|
try {
|
|
167
168
|
const repo = await githubRepos.makeRepoPublic(token, ref);
|
|
168
|
-
res.json({ ok: true, repo, private_deployable:
|
|
169
|
+
res.json({ ok: true, repo, private_deployable: true });
|
|
169
170
|
} catch (err) {
|
|
170
171
|
if (err && err.status === 401) {
|
|
171
172
|
await db.deleteGithubToken({ builderId: req.builder.id }).catch(() => {});
|
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.982'; // 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');
|