rvlib-shipkit 1.4.0 → 1.5.0
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/CHANGELOG.md +68 -0
- package/README.md +17 -17
- package/package.json +3 -2
- package/src/check.ts +68 -95
- package/src/checks/SK001-package-json-shape.check.ts +6 -2
- package/src/checks/SK002-contracts-fulfillness.check.ts +4 -0
- package/src/checks/SK003-claude-md-shape.check.ts +5 -1
- package/src/checks/SK004-gitignore-policy.check.ts +7 -4
- package/src/checks/SK005-rvlib-versions.check.ts +53 -21
- package/src/checks/SK006-npm-publish-safety.check.ts +4 -0
- package/src/checks/SK007-github-visibility.check.ts +4 -2
- package/src/checks/SK008-reviewer-agent.check.ts +6 -4
- package/src/checks/SK009-commit-ai-tells.check.ts +4 -2
- package/src/checks/SK010-undeclared-unit.check.ts +4 -0
- package/src/checks/SK011-sibling-unit-ranges.check.ts +4 -0
- package/src/checks/SK012-config-barrel-import.check.ts +5 -1
- package/src/checks/SK013-one-way-in-policy.check.ts +7 -3
- package/src/checks/SK014-prose-ste.check.ts +4 -0
- package/src/checks/SK015-nested-repo.check.ts +4 -2
- package/src/checks/SK016-legacy-check-shapes.check.ts +13 -12
- package/src/checks/SK017-vscode-hub-task.check.ts +22 -7
- package/src/checks/SK018-test-files-run.check.ts +6 -2
- package/src/checks/SK019-tests-run-in-units.check.ts +8 -4
- package/src/checks/SK020-self-spawning-check.check.ts +4 -0
- package/src/checks/SK022-machine-runs-the-pin.check.ts +6 -4
- package/src/checks/SK024-identity-colors.check.ts +5 -3
- package/src/checks/SK025-release-drift.check.ts +4 -5
- package/src/checks/SK026-asks-autoclose.check.ts +4 -4
- package/src/checks/SK027-agent-wiki-links.check.ts +4 -1
- package/src/checks/SK028-readme.check.ts +5 -1
- package/src/checks/SK029-explicit-check-files.check.ts +95 -0
- package/src/cli/cmd_Asks.ts +5 -5
- package/src/cli/cmd_Assets.ts +7 -7
- package/src/cli/cmd_Bump.ts +12 -11
- package/src/cli/cmd_Check.ts +8 -7
- package/src/cli/cmd_Checks.ts +7 -6
- package/src/cli/cmd_Ci.ts +12 -11
- package/src/cli/cmd_Commands.ts +16 -13
- package/src/cli/cmd_ConfigKeys.ts +7 -6
- package/src/cli/cmd_Contracts.ts +3 -3
- package/src/cli/cmd_CrossRepoActivity.ts +6 -5
- package/src/cli/cmd_Fleet.ts +9 -7
- package/src/cli/cmd_GlobalProcesses.ts +116 -4
- package/src/cli/cmd_Home.ts +23 -17
- package/src/cli/cmd_Hooks.ts +6 -5
- package/src/cli/cmd_Init.ts +5 -4
- package/src/cli/cmd_Keys.ts +8 -7
- package/src/cli/cmd_Mv.ts +7 -6
- package/src/cli/cmd_Needs.ts +4 -3
- package/src/cli/cmd_Overview.ts +4 -3
- package/src/cli/cmd_Pb.ts +2 -2
- package/src/cli/cmd_Planned.ts +6 -5
- package/src/cli/cmd_Prose.ts +5 -5
- package/src/cli/cmd_Reflect.ts +7 -7
- package/src/cli/cmd_Release.ts +6 -5
- package/src/cli/cmd_Releases.ts +5 -4
- package/src/cli/cmd_Serve.ts +4 -3
- package/src/cli/cmd_Smoke.ts +5 -4
- package/src/cli/cmd_Start.ts +7 -6
- package/src/cli/cmd_Status.ts +4 -3
- package/src/cli/cmd_Tui.ts +3 -2
- package/src/cli/cmd_When.ts +71 -0
- package/src/cli/cmd_Wiki.ts +4 -4
- package/src/cli.ts +7 -2
- package/src/collections/checks.ts +2 -0
- package/src/collections/collection-types.ts +9 -4
- package/src/core/canonical-scripts.ts +1 -1
- package/src/core/check-runner.ts +37 -20
- package/src/core/config-rewrite.ts +37 -24
- package/src/core/discover-checks.ts +23 -2
- package/src/core/legacy-shapes.ts +39 -10
- package/src/core/one-way-in-policy.ts +2 -2
- package/src/core/outcome-words.ts +18 -2
- package/src/core/port-check-cli.ts +52 -0
- package/src/core/port-check-file.ts +295 -0
- package/src/core/port-check-files.ts +31 -0
- package/src/core/retention.ts +0 -14
- package/src/core/run-check.ts +12 -9
- package/src/core/run-ci.ts +8 -4
- package/src/core/run-fix.ts +30 -23
- package/src/core/run-fixers.ts +42 -5
- package/src/core/runs-matrix.ts +179 -0
- package/src/core/runs.ts +242 -0
- package/src/core/update-policy.ts +20 -0
- package/src/core/when-block.ts +29 -30
- package/src/core/when.ts +41 -103
- package/src/core/wire.ts +10 -14
- package/src/fleet/scan-fleet.ts +20 -4
- package/src/fleet/update-fleet.ts +6 -6
- package/src/home/home-launchd.ts +20 -4
- package/src/home/home-routes.ts +12 -1
- package/src/home/hubs.ts +5 -5
- package/src/home/pm.ts +8 -0
- package/src/home/run-pm.ts +1 -0
- package/src/home/serve-web.ts +2 -2
- package/src/home/supervise.ts +24 -1
- package/src/hooks/run-hooks.ts +48 -4
- package/src/init-repo.ts +6 -6
- package/src/ledger/gate-checks.ts +113 -59
- package/src/ledger/git-key.ts +5 -1
- package/src/ledger/ledger.ts +5 -5
- package/src/ledger/toolchain-identity.ts +45 -27
- package/src/plan/repo-source.ts +18 -39
- package/src/release/run-release.ts +5 -0
- package/src/repo-config.ts +30 -55
- package/src/rules/rule.ts +2 -2
- package/src/scaffold/files/agent-coding.md +1 -1
- package/src/scaffold/files/agent-index.md +1 -1
- package/src/scaffold/files/verify-skill.md +2 -2
- package/src/serve/hub-app/activity-st.ts +5 -4
- package/src/serve/hub-app/activity-view.tsx +1 -1
- package/src/serve/hub-app/admin-view.tsx +13 -11
- package/src/serve/hub-app/check-view.tsx +66 -46
- package/src/serve/hub-app/check-words.ts +1 -11
- package/src/serve/hub-app/checks-svc.ts +14 -35
- package/src/serve/hub-app/col-tables-view.tsx +13 -6
- package/src/serve/hub-app/cols-svc.ts +5 -4
- package/src/serve/hub-app/dock-st.ts +3 -2
- package/src/serve/hub-app/dock-view.tsx +36 -0
- package/src/serve/hub-app/dock.css +6 -0
- package/src/serve/hub-app/float-cards.tsx +30 -0
- package/src/serve/hub-app/floating.tsx +41 -4
- package/src/serve/hub-app/git-st.ts +42 -0
- package/src/serve/hub-app/git-view.tsx +61 -0
- package/src/serve/hub-app/hash-route.ts +45 -1
- package/src/serve/hub-app/header-bar.tsx +62 -72
- package/src/serve/hub-app/health-svc.ts +3 -2
- package/src/serve/hub-app/home-st.ts +14 -2
- package/src/serve/hub-app/home-strip.css +43 -8
- package/src/serve/hub-app/home-strip.tsx +111 -47
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +14 -4
- package/src/serve/hub-app/hub-types.ts +5 -2
- package/src/serve/hub-app/hub.css +65 -43
- package/src/serve/hub-app/icons.tsx +11 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/keys.ts +5 -2
- package/src/serve/hub-app/live-svc.ts +3 -2
- package/src/serve/hub-app/machine-st.ts +18 -22
- package/src/serve/hub-app/plan-view.tsx +51 -0
- package/src/serve/hub-app/planned-st.ts +9 -4
- package/src/serve/hub-app/processes-view.tsx +38 -0
- package/src/serve/hub-app/projects-svc.ts +15 -10
- package/src/serve/hub-app/prose-hits-st.ts +3 -2
- package/src/serve/hub-app/reach.ts +36 -0
- package/src/serve/hub-app/readme-svc.ts +3 -2
- package/src/serve/hub-app/repo-sections.ts +117 -0
- package/src/serve/hub-app/repo-st.ts +46 -9
- package/src/serve/hub-app/sidebar.tsx +12 -37
- package/src/serve/hub-app/timeline-svc.ts +3 -2
- package/src/serve/hub-app/tree-build.ts +149 -155
- package/src/serve/hub-app/tree-row.tsx +39 -9
- package/src/serve/hub-app/tree-svc.ts +70 -49
- package/src/serve/hub-app/when-view.tsx +89 -0
- package/src/serve/hub-app/when.css +17 -0
- package/src/serve/hub-runtime.ts +3 -16
- package/src/serve/run-serve.ts +1 -1
- package/src/serve/serve-hub.ts +4 -14
- package/src/serve/hub-app/processes-svc.ts +0 -63
- package/src/serve/hub-app/repo-sections.tsx +0 -61
- package/src/serve/repo-processes.ts +0 -89
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// A repo's git page: its branch against its upstream, what is not committed,
|
|
2
|
+
// and its commits of the last week (HEAD's last one when the week holds
|
|
3
|
+
// none), with the ways out to VS Code and Fork. Every repo at once: the
|
|
4
|
+
// activity page (⌘L).
|
|
5
|
+
import { observer } from 'mobx-react-lite'
|
|
6
|
+
import { useEffect } from 'react'
|
|
7
|
+
import { isMainBranch, upstreamWords } from '../../home/activity-shape.ts'
|
|
8
|
+
import { Commit } from './activity-view.tsx'
|
|
9
|
+
import { Icon, ICON } from './icons.tsx'
|
|
10
|
+
import type { RepoSt } from './repo-st.ts'
|
|
11
|
+
|
|
12
|
+
const KIND_WORD = { staged: 'staged', modified: 'modified', untracked: 'new' } as const
|
|
13
|
+
|
|
14
|
+
export const GitPage = observer(({ app }: { app: RepoSt }) => {
|
|
15
|
+
const g = app.git
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
g.start()
|
|
18
|
+
return () => g.stop()
|
|
19
|
+
}, [g])
|
|
20
|
+
const r = g.row
|
|
21
|
+
const m = app.home.machine
|
|
22
|
+
const now = app.clock.now
|
|
23
|
+
const branch = r == null ? '' : r.branch ?? (r.detached == null ? '' : `detached ${r.detached}`)
|
|
24
|
+
return (
|
|
25
|
+
<div className="colcard checkcard">
|
|
26
|
+
<div className="chead">
|
|
27
|
+
<span className="tid">git</span>
|
|
28
|
+
<h3 className="ctitle">{app.name}</h3>
|
|
29
|
+
<span className="sp" />
|
|
30
|
+
{m == null ? null : <button className="hicon" title="open in VS Code" aria-label={`open ${app.key} in VS Code`} onClick={() => m.openInCode(app.key)}><Icon d={ICON.vscode} /></button>}
|
|
31
|
+
<button className="hicon" title="open in Fork" aria-label={`open ${app.key} in Fork`} onClick={() => void app.home.activity.openFork(app.key)}><Icon d={ICON.fork} /></button>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="usays">its branch, what is not committed, and its commits of the last week. Every repo at once: the activity page (⌘L)</div>
|
|
34
|
+
{g.error !== '' ? <div className="warnline">{g.error}</div> : null}
|
|
35
|
+
{r == null ? (g.error === '' ? <div className="dim twait"><span className="spin" />reading git…</div> : null) : r.error != null ? <div className="tl red">git failed: {r.error}</div> : (
|
|
36
|
+
<>
|
|
37
|
+
<div className="cfacts">
|
|
38
|
+
<div className="cf"><div className="ck">branch</div><div className={`cv tl ${isMainBranch(r.branch) ? 'ok' : 'warn'}`}>{branch}</div></div>
|
|
39
|
+
<div className="cf"><div className="ck">upstream</div><div className="cv">{upstreamWords(r)}</div></div>
|
|
40
|
+
</div>
|
|
41
|
+
<section className="csec">
|
|
42
|
+
<div className="cstitle">not committed ({r.files.length})</div>
|
|
43
|
+
{r.files.length === 0 ? <div className="dim">nothing: the tree is clean</div> : (
|
|
44
|
+
<table className="gitfiles"><tbody>{r.files.map((f) => <tr key={f.path}><td className={`gitkind ${f.kind}`}>{KIND_WORD[f.kind]}</td><td className="gitpath">{f.path}</td></tr>)}</tbody></table>
|
|
45
|
+
)}
|
|
46
|
+
</section>
|
|
47
|
+
<section className="csec">
|
|
48
|
+
<div className="cstitle">commits, last week ({r.commits.length})</div>
|
|
49
|
+
<table className="gitcommits">
|
|
50
|
+
<tbody>
|
|
51
|
+
{r.commits.map((c) => <tr key={c.sha}><Commit c={c} now={now} old={false} /></tr>)}
|
|
52
|
+
{r.last != null ? <tr><Commit c={r.last} now={now} old /></tr> : null}
|
|
53
|
+
{r.commits.length === 0 && r.last == null ? <tr><td className="dim">no commit yet</td></tr> : null}
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
</section>
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
})
|
|
@@ -22,9 +22,25 @@ export type HashSel =
|
|
|
22
22
|
| { t: 'tests'; check: string | null }
|
|
23
23
|
/** what administers the repo: archive, reload its hub, keep it active */
|
|
24
24
|
| { t: 'admin' }
|
|
25
|
+
/** the repo's README, what a repo shows with nothing else named */
|
|
26
|
+
| { t: 'readme' }
|
|
27
|
+
/** the global processes the repo declares, one card each, `id` the one
|
|
28
|
+
* a tree row opened (scrolled to and lit), null for none */
|
|
29
|
+
| { t: 'processes'; id: string | null }
|
|
30
|
+
/** what the machine plans to run in this repo, and when */
|
|
31
|
+
| { t: 'plan' }
|
|
32
|
+
/** the repo's git: branch, upstream, uncommitted files, recent commits */
|
|
33
|
+
| { t: 'git' }
|
|
34
|
+
/** what runs when: every check on every context, with the legend */
|
|
35
|
+
| { t: 'when' }
|
|
36
|
+
/** the checks one owner ships: shipkit's, or the repo's own */
|
|
37
|
+
| { t: 'checks'; from: CheckOwner }
|
|
38
|
+
|
|
39
|
+
/** whose a gate check is */
|
|
40
|
+
export type CheckOwner = 'shipkit' | 'repo'
|
|
25
41
|
|
|
26
42
|
export type ParsedHash = {
|
|
27
|
-
kind: 'asset' | 'cat' | 'col' | 'item' | 'status' | 'tests' | 'admin' | 'none'
|
|
43
|
+
kind: 'asset' | 'cat' | 'col' | 'item' | 'status' | 'tests' | 'admin' | 'readme' | 'processes' | 'plan' | 'git' | 'when' | 'checks' | 'none'
|
|
28
44
|
/** the collection, for an item. null for everything else */
|
|
29
45
|
col: string | null
|
|
30
46
|
id: string
|
|
@@ -40,6 +56,12 @@ const CAT = 'cat-'
|
|
|
40
56
|
const STATUS = 'page:status'
|
|
41
57
|
const TESTS = 'page:tests'
|
|
42
58
|
const ADMIN = 'page:admin'
|
|
59
|
+
const README = 'page:readme'
|
|
60
|
+
const PROCESSES = 'page:processes'
|
|
61
|
+
const PLAN = 'page:plan'
|
|
62
|
+
const GIT = 'page:git'
|
|
63
|
+
const WHEN = 'page:when'
|
|
64
|
+
const CHECKS = 'page:checks:'
|
|
43
65
|
|
|
44
66
|
/** what this selection puts in the url. null only for no selection at all,
|
|
45
67
|
* and then the hash is left alone rather than wiped */
|
|
@@ -53,6 +75,12 @@ export function hashFor(sel: HashSel | null): string | null {
|
|
|
53
75
|
case 'status': return STATUS
|
|
54
76
|
case 'tests': return sel.check == null ? TESTS : `${TESTS}:${sel.check}`
|
|
55
77
|
case 'admin': return ADMIN
|
|
78
|
+
case 'readme': return README
|
|
79
|
+
case 'processes': return sel.id == null ? PROCESSES : `${PROCESSES}:${sel.id}`
|
|
80
|
+
case 'plan': return PLAN
|
|
81
|
+
case 'git': return GIT
|
|
82
|
+
case 'when': return WHEN
|
|
83
|
+
case 'checks': return `${CHECKS}${sel.from}`
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
86
|
|
|
@@ -62,6 +90,16 @@ export function parseHash(raw: string): ParsedHash {
|
|
|
62
90
|
if (raw === STATUS) return { kind: 'status', col: null, id: '', tools: null }
|
|
63
91
|
if (raw === TESTS) return { kind: 'tests', col: null, id: '', tools: null }
|
|
64
92
|
if (raw === ADMIN) return { kind: 'admin', col: null, id: '', tools: null }
|
|
93
|
+
if (raw === README) return { kind: 'readme', col: null, id: '', tools: null }
|
|
94
|
+
if (raw === PROCESSES) return { kind: 'processes', col: null, id: '', tools: null }
|
|
95
|
+
if (raw === PLAN) return { kind: 'plan', col: null, id: '', tools: null }
|
|
96
|
+
if (raw === GIT) return { kind: 'git', col: null, id: '', tools: null }
|
|
97
|
+
if (raw === WHEN) return { kind: 'when', col: null, id: '', tools: null }
|
|
98
|
+
if (raw === `${CHECKS}shipkit` || raw === `${CHECKS}repo`) return { kind: 'checks', col: null, id: raw.slice(CHECKS.length), tools: null }
|
|
99
|
+
if (raw.startsWith(`${PROCESSES}:`)) {
|
|
100
|
+
const id = raw.slice(PROCESSES.length + 1)
|
|
101
|
+
return id === '' ? none : { kind: 'processes', col: null, id, tools: null }
|
|
102
|
+
}
|
|
65
103
|
// a check id carries colons of its own (`test:rvlib-shipkit`)
|
|
66
104
|
if (raw.startsWith(`${TESTS}:`)) {
|
|
67
105
|
const check = raw.slice(TESTS.length + 1)
|
|
@@ -104,6 +142,12 @@ export function selOf(p: ParsedHash): HashSel | null {
|
|
|
104
142
|
case 'status': return { t: 'status' }
|
|
105
143
|
case 'tests': return { t: 'tests', check: p.id === '' ? null : p.id }
|
|
106
144
|
case 'admin': return { t: 'admin' }
|
|
145
|
+
case 'readme': return { t: 'readme' }
|
|
146
|
+
case 'processes': return { t: 'processes', id: p.id === '' ? null : p.id }
|
|
147
|
+
case 'plan': return { t: 'plan' }
|
|
148
|
+
case 'git': return { t: 'git' }
|
|
149
|
+
case 'when': return { t: 'when' }
|
|
150
|
+
case 'checks': return { t: 'checks', from: p.id as CheckOwner }
|
|
107
151
|
}
|
|
108
152
|
}
|
|
109
153
|
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
// A repo's pieces of the top bar
|
|
2
|
-
// and the
|
|
3
|
-
//
|
|
4
|
-
// (
|
|
5
|
-
//
|
|
1
|
+
// A repo's pieces of the top bar. On the right: modernize while it is behind
|
|
2
|
+
// and the hub's own processes (RepoTools), then a spinner while something
|
|
3
|
+
// runs, its words on hover, and one check… button that opens the two runs
|
|
4
|
+
// that are not one check (RepoRun), then at the far right end the editor
|
|
5
|
+
// icons (RepoOpeners). The home strip puts the repo's global processes right
|
|
6
|
+
// after its name (home-strip.tsx). The bar carries no trouble text:
|
|
7
|
+
// the hub's own trouble is a card floating under it (hubCards, float-cards.tsx).
|
|
8
|
+
// On the home they ride in the home strip (home-strip.tsx); a hub of its own
|
|
6
9
|
// draws them in its own header, with its name. The repo's sections are the
|
|
7
|
-
// left pane's (repo-sections.
|
|
8
|
-
// bottom of the page (dock-view.tsx).
|
|
10
|
+
// top rows of the left pane's tree (repo-sections.ts). What runs, drawn, is
|
|
11
|
+
// the dock at the bottom of the page (dock-view.tsx).
|
|
9
12
|
import { observer } from 'mobx-react-lite'
|
|
10
13
|
import { Icon, ICON } from './icons.tsx'
|
|
14
|
+
import { Pop, Tip } from './floating.tsx'
|
|
15
|
+
import { FloatCards, type Card } from './float-cards.tsx'
|
|
11
16
|
import type { RepoSt } from './repo-st.ts'
|
|
12
17
|
|
|
13
18
|
/** the repo's hub has not answered on the home: its reason and its fix show
|
|
@@ -17,56 +22,64 @@ export const notUp = (app: RepoSt): boolean => {
|
|
|
17
22
|
const m = app.home.machine
|
|
18
23
|
if (m == null || app.started) return false
|
|
19
24
|
if (!app.cols.cached) return true
|
|
20
|
-
|
|
25
|
+
// the daemon silent: the hub's state is unknown, not down, so its last screen stays
|
|
26
|
+
return m.fresh && m.down === '' && m.worker(app.key)?.status !== 'up'
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
/** what is running, in
|
|
24
|
-
* most, else the newest run the record has in flight.
|
|
25
|
-
|
|
26
|
-
const RunState = observer(({ app }: { app: RepoSt }) => {
|
|
29
|
+
/** what is running, in words: the hub's own run first, since it says the
|
|
30
|
+
* most, else the newest run the record has in flight. Null while idle */
|
|
31
|
+
export const runWords = (app: RepoSt): string | null => {
|
|
27
32
|
const own = app.checks.strip
|
|
33
|
+
if (own !== '') return own
|
|
28
34
|
const run = app.live.view?.runs[0]
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
if (run != null) return `${run.label} · ${run.done} of ${run.total}${run.reds > 0 ? ` · ${run.reds} red so far` : ''}`
|
|
36
|
+
return app.checks.busy ? 'running' : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** a spinner while something runs, its words in a tooltip that opens at
|
|
40
|
+
* once; nothing at all while idle */
|
|
41
|
+
const RunState = observer(({ app }: { app: RepoSt }) => {
|
|
42
|
+
const text = runWords(app)
|
|
43
|
+
if (text == null) return null
|
|
31
44
|
return (
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<span className="htext">{text}</span>
|
|
36
|
-
</span>
|
|
45
|
+
<Tip className="hbusy" instant placement="bottom" tabIndex={0} role="status" aria-label={`running: ${text}`} content={text}>
|
|
46
|
+
<span className="spin" />
|
|
47
|
+
</Tip>
|
|
37
48
|
)
|
|
38
49
|
})
|
|
39
50
|
|
|
40
51
|
export const HEALTH_TITLE = 'run the health pass: every check but the build steps, as shipkit check runs them (creds, probes, rules, contracts)'
|
|
41
52
|
export const GATE_TITLE = 'run the gate: every check shipkit ci runs, the build steps included (lint, typecheck, tests)'
|
|
42
53
|
|
|
43
|
-
/** the
|
|
44
|
-
|
|
45
|
-
export const RepoRun = observer(({ app }: { app: RepoSt }) => {
|
|
54
|
+
/** the two runs, each with what it does; an entry closes the menu as it starts */
|
|
55
|
+
export const CheckMenu = observer(({ app, close }: { app: RepoSt; close: () => void }) => {
|
|
46
56
|
const u = app.checks
|
|
47
57
|
return (
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
<Icon d={ICON.health} /><span className="lbl">health pass</span>
|
|
58
|
+
<div className="hmenu">
|
|
59
|
+
<button className="hmitem" disabled={u.busy} onClick={() => { close(); void u.run() }}>
|
|
60
|
+
<Icon d={ICON.health} /><span><b>health pass</b><small>{HEALTH_TITLE}</small></span>
|
|
52
61
|
</button>
|
|
53
|
-
<button className="
|
|
54
|
-
<Icon d={ICON.gate} /><span
|
|
62
|
+
<button className="hmitem" disabled={u.busy} onClick={() => { close(); void u.run(undefined, { gate: true, label: 'the gate' }) }}>
|
|
63
|
+
<Icon d={ICON.gate} /><span><b>gate</b><small>{GATE_TITLE}</small></span>
|
|
55
64
|
</button>
|
|
56
|
-
</
|
|
65
|
+
</div>
|
|
57
66
|
)
|
|
58
67
|
})
|
|
59
68
|
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
|
|
69
|
+
/** the spinner while a run goes, then one small check… button with the two
|
|
70
|
+
* runs behind it. The bar ends on the right, so the spinner comes and goes
|
|
71
|
+
* to the button's left and the button never moves */
|
|
72
|
+
export const RepoRun = observer(({ app }: { app: RepoSt }) => (
|
|
73
|
+
<span className="hrun">
|
|
74
|
+
<RunState app={app} />
|
|
75
|
+
<Pop className="ghost hcheck" aria-label="run a check" label="check…" placement="bottom-end">{(close) => <CheckMenu app={app} close={close} />}</Pop>
|
|
76
|
+
</span>
|
|
68
77
|
))
|
|
69
78
|
|
|
79
|
+
/** what is wrong with the hub itself, one card per problem */
|
|
80
|
+
export const hubCards = (app: RepoSt): Card[] =>
|
|
81
|
+
app.cols.hubStatus.map((p) => ({ key: `hub:${p.id}`, tone: p.severity === 'red' ? 'red' : 'warn', body: p.text }))
|
|
82
|
+
|
|
70
83
|
/** modernize, while the repo runs an older shipkit than the home */
|
|
71
84
|
const Modernize = observer(({ app }: { app: RepoSt }) => {
|
|
72
85
|
const m = app.home.machine
|
|
@@ -88,48 +101,25 @@ export const RepoOpeners = observer(({ app }: { app: RepoSt }) => {
|
|
|
88
101
|
)
|
|
89
102
|
})
|
|
90
103
|
|
|
91
|
-
/** the right side of the bar for a repo, before the
|
|
92
|
-
* the openers, which close it */
|
|
104
|
+
/** the right side of the bar for a repo, before the openers, which close it */
|
|
93
105
|
export const RepoTools = observer(({ app }: { app: RepoSt }) => (
|
|
94
106
|
<>
|
|
95
|
-
<ProcessChips app={app} />
|
|
96
|
-
<HubStatus app={app} />
|
|
97
107
|
<Modernize app={app} />
|
|
98
108
|
</>
|
|
99
109
|
))
|
|
100
110
|
|
|
101
111
|
/** the bar of a hub of its own: its name, then the same pieces the home
|
|
102
|
-
* strip carries for a repo */
|
|
112
|
+
* strip carries for a repo, and its trouble floating under it */
|
|
103
113
|
export const HeaderBar = observer(({ app }: { app: RepoSt }) => (
|
|
104
|
-
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
<>
|
|
115
|
+
<header className="hdr">
|
|
116
|
+
<span className="hname" style={app.name === '' ? undefined : { color: app.home.projects.identityOf(app.name).colorDark }}>{app.name !== '' ? app.name : 'hub'}</span>
|
|
117
|
+
<span className="sp" />
|
|
118
|
+
<RepoTools app={app} />
|
|
119
|
+
<RepoRun app={app} />
|
|
120
|
+
<RepoOpeners app={app} />
|
|
121
|
+
</header>
|
|
122
|
+
<FloatCards cards={hubCards(app)} />
|
|
123
|
+
</>
|
|
111
124
|
))
|
|
112
125
|
|
|
113
|
-
/** one switch per process the repo declares (serve.processes): its dot, its
|
|
114
|
-
* id, a click starts or stops it, and a link once it serves */
|
|
115
|
-
export const ProcessChips = observer(({ app }: { app: RepoSt }) => {
|
|
116
|
-
const s = app.processes
|
|
117
|
-
if (s.list.length === 0) return null
|
|
118
|
-
return (
|
|
119
|
-
<span className="hprocs">
|
|
120
|
-
{s.list.map((p) => {
|
|
121
|
-
const on = p.status === 'up' || p.status === 'starting'
|
|
122
|
-
const title = [p.cmd, p.note, p.external ? 'started outside the hub' : null, p.tail.at(-1)].filter((x) => x != null && x !== '').join('\n')
|
|
123
|
-
return (
|
|
124
|
-
<span key={p.id} className="hproc">
|
|
125
|
-
<button className={`ghost ${p.status}`} title={title} disabled={p.status === 'stopping' || (on && p.external)}
|
|
126
|
-
onClick={() => void s.act(p.id, on ? 'stop' : 'start')}>
|
|
127
|
-
<i className={`pdot ${p.status}`} />{p.id}
|
|
128
|
-
</button>
|
|
129
|
-
{p.url != null && p.status === 'up' ? <a href={p.url} target="_blank" rel="noreferrer" title={p.url}>↗</a> : null}
|
|
130
|
-
</span>
|
|
131
|
-
)
|
|
132
|
-
})}
|
|
133
|
-
</span>
|
|
134
|
-
)
|
|
135
|
-
})
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// exists so the whole text of a run stays reachable.
|
|
7
7
|
import { makeAutoObservable, runInAction } from 'mobx'
|
|
8
8
|
import type { RepoSt } from './repo-st.ts'
|
|
9
|
+
import { ownTrouble, reach } from './reach.ts'
|
|
9
10
|
|
|
10
11
|
export class HealthSvc {
|
|
11
12
|
statusMd: string | null = null
|
|
@@ -25,10 +26,10 @@ export class HealthSvc {
|
|
|
25
26
|
|
|
26
27
|
async load(): Promise<void> {
|
|
27
28
|
try {
|
|
28
|
-
const s = (await (await
|
|
29
|
+
const s = (await (await reach(this.st.url('/api/status.json'))).json()) as { statusMd: string | null }
|
|
29
30
|
runInAction(() => { this.statusMd = s.statusMd })
|
|
30
31
|
} catch (e) {
|
|
31
|
-
runInAction(() => { this.statusMd = `FAIL status fetch broke: ${
|
|
32
|
+
runInAction(() => { this.statusMd = ownTrouble(e, this.st.home.machine, (m) => `FAIL status fetch broke: ${m}`) || this.statusMd })
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -20,6 +20,7 @@ import { PlannedSt } from './planned-st.ts'
|
|
|
20
20
|
import { ActivitySt } from './activity-st.ts'
|
|
21
21
|
import { ProjectsSvc } from './projects-svc.ts'
|
|
22
22
|
import { parseFilter, RepoSt } from './repo-st.ts'
|
|
23
|
+
import { openSection, repoSections, sectionOfRepo } from './repo-sections.ts'
|
|
23
24
|
|
|
24
25
|
/** the longest one repo's read ahead holds the next one back */
|
|
25
26
|
export const PREFETCH_WAIT_MS = 10_000
|
|
@@ -169,8 +170,19 @@ export class HomeSt {
|
|
|
169
170
|
for (const r of this.repos.values()) r.dispose()
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
/** a list click, a repo row: that repo, in place, as a history entry
|
|
173
|
+
/** a list click, a repo row: that repo, in place, as a history entry,
|
|
174
|
+
* on the section the repo left showed. One already inside that section
|
|
175
|
+
* keeps its own page there, and one that has no such section opens its
|
|
176
|
+
* README. Written before the repo shows, so the one history entry
|
|
177
|
+
* already names it */
|
|
173
178
|
select(name: string): void {
|
|
179
|
+
const from = this.page === 'repo' ? this.shown : null
|
|
180
|
+
const to = this.repo(name)
|
|
181
|
+
const want = from == null || from === to ? null : sectionOfRepo(from)
|
|
182
|
+
if (want != null && sectionOfRepo(to) !== want) {
|
|
183
|
+
const known = to.cols.loaded || to.cols.cached
|
|
184
|
+
openSection(to, known && !repoSections(to).some((s) => s.id === want) ? 'readme' : want)
|
|
185
|
+
}
|
|
174
186
|
this.show('repo', name, 'push')
|
|
175
187
|
}
|
|
176
188
|
|
|
@@ -178,7 +190,7 @@ export class HomeSt {
|
|
|
178
190
|
* before the repo is shown, so the one history entry already names it */
|
|
179
191
|
openCheck(repo: string, check: string): void {
|
|
180
192
|
this.repo(repo).cols.select('checks', `check:${check}`)
|
|
181
|
-
this.
|
|
193
|
+
this.show('repo', repo, 'push')
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
/** a link to a page of the home: the same page, in place */
|
|
@@ -10,14 +10,42 @@
|
|
|
10
10
|
.hswatch input{width:16px;height:16px;padding:0;border:1px solid var(--line);border-radius:3px;background:none;cursor:pointer}
|
|
11
11
|
.hswatch input::-webkit-color-swatch-wrapper{padding:0}
|
|
12
12
|
.hswatch input::-webkit-color-swatch{border:none;border-radius:2px}
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
.htoast{
|
|
20
|
-
.htoast
|
|
13
|
+
/* trouble, as cards (float-cards.tsx), never taking room, so one coming or
|
|
14
|
+
going moves nothing: at the top right, in a zero height box right after
|
|
15
|
+
the bar, so just under it whatever its height. The words wrap, never cut */
|
|
16
|
+
.htoasts{position:absolute;right:var(--s3);top:var(--s2);z-index:25;display:flex;flex-direction:column;align-items:flex-end;gap:var(--s1);max-width:min(60ch,calc(100% - 2 * var(--s3)));pointer-events:none}
|
|
17
|
+
.hanchor{position:relative;height:0;flex:none;z-index:25}
|
|
18
|
+
.htoast{max-width:100%;padding:var(--s1) var(--s2);background:var(--panel);border:1px solid var(--red);border-radius:4px;box-shadow:0 6px 20px var(--shadow);font-size:12px;line-height:1.45;color:var(--errink);white-space:normal;overflow-wrap:anywhere;pointer-events:auto}
|
|
19
|
+
.htoast b{color:var(--red);margin-right:var(--s1)}
|
|
20
|
+
.htoast.warn{border-color:var(--warn);color:var(--warn)}
|
|
21
|
+
.htoast.warn b{color:var(--warn)}
|
|
22
|
+
.htoast .hproc{vertical-align:middle}
|
|
23
|
+
/* the bar's run: one small check… button, then a spinner while a run goes,
|
|
24
|
+
its words in a tooltip (header-bar.tsx) */
|
|
25
|
+
.hcheck{height:22px;padding:0 var(--s2);font-size:11.5px;flex:none}
|
|
26
|
+
.hbusy{display:inline-flex;align-items:center;justify-content:center;width:18px;min-width:18px;height:22px;flex:none;border-radius:3px;cursor:default;outline:none}
|
|
27
|
+
.hbusy:focus-visible{box-shadow:0 0 0 1px var(--accent)}
|
|
28
|
+
/* a popover opened on a click (floating.tsx Pop) */
|
|
29
|
+
.fpop{z-index:40;width:max-content;max-width:min(44ch,90vw);padding:var(--s1);background:var(--panel2);border:1px solid var(--line);border-radius:4px;box-shadow:0 6px 20px var(--shadow);outline:none}
|
|
30
|
+
.hmenu{display:flex;flex-direction:column;gap:var(--s0)}
|
|
31
|
+
.fpopw{max-width:min(96ch,94vw)}
|
|
32
|
+
.hmenu2{display:flex;align-items:stretch}
|
|
33
|
+
.hmacts{flex:none;min-width:24ch;max-width:36ch}
|
|
34
|
+
.hmabout{flex:1 1 auto;min-width:0;max-width:56ch;max-height:60vh;overflow:auto;display:flex;flex-direction:column;gap:var(--s1);margin-left:var(--s1);padding:var(--s1) var(--s2);border-left:1px solid var(--line);user-select:text}
|
|
35
|
+
.hmabout .md{overflow-wrap:anywhere}
|
|
36
|
+
.hmabout .md>:first-child{margin-top:0}
|
|
37
|
+
.hmlog{display:block;font-size:11px;color:var(--dim);white-space:pre-wrap;overflow-wrap:anywhere}
|
|
38
|
+
@media (max-width:640px){.hmenu2{flex-direction:column}.hmabout{margin:var(--s1) 0 0;border-left:0;border-top:1px solid var(--line)}}
|
|
39
|
+
.hmitem{display:flex;align-items:flex-start;gap:var(--s2);width:100%;padding:var(--s1) var(--s2);border:0;border-radius:3px;background:none;color:var(--ink);text-align:left;white-space:normal;cursor:pointer}
|
|
40
|
+
.hmitem:hover:not(:disabled),.hmitem:focus-visible{background:var(--panel)}
|
|
41
|
+
.hmitem:disabled{opacity:.45;cursor:default}
|
|
42
|
+
.hmitem .hico{flex:none;width:14px;height:14px;margin-top:2px}
|
|
43
|
+
.hmitem span{display:flex;flex-direction:column;gap:2px}
|
|
44
|
+
.hmitem b{font-size:12.5px;font-weight:600}
|
|
45
|
+
.hmitem small{font-size:11.5px;line-height:1.4;color:var(--dim)}
|
|
46
|
+
.hmhead{display:flex;align-items:center;gap:var(--s1);padding:var(--s1) var(--s2) 0;font-size:12.5px}
|
|
47
|
+
.hmhead span{color:var(--dim)}
|
|
48
|
+
.hmnote{display:block;font-size:11.5px;line-height:1.4;color:var(--dim);overflow-wrap:anywhere}
|
|
21
49
|
.hdot{display:inline-block;width:8px;height:8px;border-radius:4px;margin-right:var(--s1);background:var(--line)}
|
|
22
50
|
.hdot.up{background:var(--ok)}.hdot.starting,.hdot.stopping{background:var(--warn)}.hdot.crashed{background:var(--red)}
|
|
23
51
|
.hres{display:inline-flex;align-items:center;padding:var(--s0) var(--s2);font-size:12px}
|
|
@@ -126,3 +154,10 @@
|
|
|
126
154
|
.wbar > i{display:block;height:100%;width:var(--p,0%);background:var(--ok)}
|
|
127
155
|
.wbar.mid > i{background:var(--warn)}
|
|
128
156
|
.wbar.hi > i{background:var(--red)}
|
|
157
|
+
|
|
158
|
+
/* a process card on the processes page: the menu's two panels at page width */
|
|
159
|
+
.pcard .hmacts{max-width:none}
|
|
160
|
+
.pcard .hmabout{max-width:none;max-height:none}
|
|
161
|
+
.pcard>.cstitle{margin:var(--s3) 0 var(--s1)}
|
|
162
|
+
.pcard .hlog{margin-top:0}
|
|
163
|
+
.pcard.lit{border-color:var(--accent)}
|