rvlib-shipkit 0.38.0 → 1.0.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 +371 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/generate-roll.ts +1 -1
- package/src/assets/kit-config.ts +6 -6
- package/src/assets/run-assets.ts +14 -10
- package/src/assets/server/studio-server.ts +6 -5
- package/src/check.ts +343 -0
- package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
- package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
- package/src/checks/SK003-claude-md-shape.check.ts +66 -0
- package/src/checks/SK004-gitignore-policy.check.ts +309 -0
- package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
- package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
- package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
- package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
- package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
- package/src/checks/SK010-undeclared-unit.check.ts +111 -0
- package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
- package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
- package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
- package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
- package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
- package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
- package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
- package/src/checks/SK018-test-files-run.check.ts +28 -0
- package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
- package/src/checks/SK020-self-spawning-check.check.ts +147 -0
- package/src/checks/contracts.check.ts +20 -0
- package/src/cli/cmd_Asks.ts +3 -2
- package/src/cli/cmd_Assets.ts +2 -2
- package/src/cli/cmd_Bump.ts +1 -1
- package/src/cli/cmd_Check.ts +6 -6
- package/src/cli/cmd_Checks.ts +95 -0
- package/src/cli/cmd_Ci.ts +48 -13
- package/src/cli/cmd_Commands.ts +2 -6
- package/src/cli/cmd_ConfigKeys.ts +69 -0
- package/src/cli/cmd_Contracts.ts +1 -1
- package/src/cli/cmd_Fleet.ts +6 -2
- package/src/cli/cmd_Hooks.ts +1 -1
- package/src/cli/cmd_Keys.ts +43 -18
- package/src/cli/cmd_Mv.ts +2 -2
- package/src/cli/cmd_Prose.ts +10 -1
- package/src/cli/cmd_Releases.ts +2 -2
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli/cmd_Start.ts +6 -6
- package/src/cli/cmd_Status.ts +25 -0
- package/src/cli/cmd_Tui.ts +2 -2
- package/src/cli.ts +7 -4
- package/src/collections/actions.ts +4 -4
- package/src/collections/checks.ts +258 -0
- package/src/collections/collection-types.ts +254 -1
- package/src/collections/comfy.ts +23 -0
- package/src/collections/md-body.ts +14 -0
- package/src/collections/pings.ts +38 -12
- package/src/collections/posts.ts +19 -3
- package/src/collections/providers.ts +198 -91
- package/src/collections/reflections.ts +41 -0
- package/src/collections/state-rules.ts +73 -0
- package/src/collections/test-time.ts +288 -0
- package/src/collections/tests.ts +181 -0
- package/src/collections/wikis.ts +66 -0
- package/src/comfy/comfy-panel.ts +292 -0
- package/src/contracts/apply-contracts.ts +17 -12
- package/src/contracts/contract-types.ts +16 -4
- package/src/contracts/contracts-engine.ts +78 -42
- package/src/contracts/manifests/discord.contract.ts +6 -6
- package/src/contracts/manifests/identity.contract.ts +3 -3
- package/src/contracts/manifests/itch.contract.ts +12 -15
- package/src/contracts/manifests/social.contract.ts +5 -5
- package/src/contracts/manifests/steam.contract.ts +16 -16
- package/src/contracts/run-contracts.ts +4 -3
- package/src/core/attach.ts +96 -0
- package/src/core/canonical-scripts.ts +25 -0
- package/src/core/check-ctx.ts +204 -14
- package/src/core/check-file-names.ts +112 -0
- package/src/core/check-pass.ts +84 -0
- package/src/core/check-runner.ts +796 -0
- package/src/core/config-keys.ts +256 -0
- package/src/core/config-rewrite.ts +77 -0
- package/src/core/discover-checks.ts +166 -90
- package/src/core/discover-legacy-checks.ts +26 -0
- package/src/core/doc-block.ts +13 -0
- package/src/core/floor-resolve.ts +264 -0
- package/src/core/floor.ts +20 -137
- package/src/core/git-bin.ts +24 -0
- package/src/core/git-root.ts +108 -0
- package/src/core/instance.ts +195 -0
- package/src/core/jobs.ts +118 -0
- package/src/core/layout.ts +52 -0
- package/src/core/legacy-shapes.ts +207 -0
- package/src/core/lib-root.ts +5 -0
- package/src/core/locked-write.ts +110 -39
- package/src/core/machine-commit.ts +5 -4
- package/src/core/migrate-layout.ts +557 -0
- package/src/core/motherhome.ts +2 -8
- package/src/core/nesting.ts +4 -12
- package/src/core/one-way-in-policy.ts +73 -17
- package/src/core/outcome-words.ts +45 -0
- package/src/core/package-manager.ts +86 -0
- package/src/core/parallel.ts +21 -0
- package/src/core/process-tree.ts +44 -0
- package/src/core/raw-access.ts +93 -0
- package/src/core/read-set.ts +152 -0
- package/src/core/run-check.ts +155 -213
- package/src/core/run-ci.ts +153 -189
- package/src/core/run-fix.ts +96 -0
- package/src/core/run-fixers.ts +53 -30
- package/src/core/self-update.ts +9 -3
- package/src/core/slots.ts +83 -0
- package/src/core/spawn-timeout.ts +34 -3
- package/src/core/when-block.ts +401 -0
- package/src/core/when.ts +135 -0
- package/src/core/write-status.ts +5 -2
- package/src/hooks/leak-guard.ts +10 -8
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +35 -23
- package/src/init-repo.ts +39 -23
- package/src/ledger/check-stats.ts +155 -0
- package/src/ledger/check-table.ts +179 -0
- package/src/ledger/db.ts +255 -0
- package/src/ledger/gate-checks.ts +433 -0
- package/src/ledger/git-key.ts +116 -77
- package/src/ledger/git-scan.ts +96 -35
- package/src/ledger/ledger.ts +116 -42
- package/src/ledger/live-runs.ts +111 -0
- package/src/ledger/live-timeline.ts +97 -0
- package/src/ledger/repo-view.ts +73 -0
- package/src/ledger/run-log.ts +448 -0
- package/src/ledger/run-timeline.ts +173 -0
- package/src/ledger/store.ts +23 -12
- package/src/ledger/suite-log.ts +161 -0
- package/src/ledger/toolchain-identity.ts +7 -1
- package/src/ledger/world-cache.ts +111 -0
- package/src/mv/mv-md.ts +16 -6
- package/src/problems/extract.ts +182 -0
- package/src/problems/problem-board.ts +37 -0
- package/src/problems/problem-line.ts +77 -0
- package/src/prose/prose-view.ts +90 -0
- package/src/prose/run-prose.ts +60 -19
- package/src/prose/ste.ts +140 -24
- package/src/reflections/reflections-queue.ts +81 -0
- package/src/release/git-facts.ts +15 -9
- package/src/release/run-bump.ts +6 -5
- package/src/release/run-release.ts +25 -11
- package/src/release/run-releases.ts +2 -2
- package/src/release/unit-gate.ts +7 -54
- package/src/release/unit-resolve.ts +282 -0
- package/src/release/units.ts +33 -217
- package/src/repo-config.ts +155 -85
- package/src/rules/rule.ts +36 -96
- package/src/rules/untrack-generated.ts +5 -4
- package/src/scaffold/files/vscode-tasks.json +10 -31
- package/src/scaffold/jsonc-spans.ts +113 -0
- package/src/scaffold/project-scaffold.ts +16 -43
- package/src/scan.ts +98 -0
- package/src/serve/gate-trigger.ts +148 -0
- package/src/serve/health-schedule.ts +102 -0
- package/src/serve/hub-app/chart-view.ts +179 -0
- package/src/serve/hub-app/check-view.tsx +345 -0
- package/src/serve/hub-app/check-words.ts +164 -0
- package/src/serve/hub-app/checks-svc.ts +280 -0
- package/src/serve/hub-app/clock-svc.ts +33 -0
- package/src/serve/hub-app/col-card.tsx +23 -39
- package/src/serve/hub-app/cols-svc.ts +44 -20
- package/src/serve/hub-app/comfy-svc.ts +171 -0
- package/src/serve/hub-app/comfy-view.tsx +81 -0
- package/src/serve/hub-app/dur.ts +41 -0
- package/src/serve/hub-app/hash-route.ts +121 -0
- package/src/serve/hub-app/header-bar.tsx +117 -0
- package/src/serve/hub-app/health-svc.ts +23 -28
- package/src/serve/hub-app/help.tsx +29 -0
- package/src/serve/hub-app/hub-app.tsx +122 -119
- package/src/serve/hub-app/hub-st.ts +115 -42
- package/src/serve/hub-app/hub-types.ts +117 -2
- package/src/serve/hub-app/hub.css +534 -192
- package/src/serve/hub-app/index.html +2 -0
- package/src/serve/hub-app/live-svc.ts +174 -0
- package/src/serve/hub-app/md.ts +15 -0
- package/src/serve/hub-app/media-svc.ts +4 -1
- package/src/serve/hub-app/media-view.tsx +15 -0
- package/src/serve/hub-app/next-run.ts +68 -0
- package/src/serve/hub-app/project-gutter.css +65 -0
- package/src/serve/hub-app/project-gutter.tsx +128 -0
- package/src/serve/hub-app/projects-svc.ts +226 -0
- package/src/serve/hub-app/prose-hits-st.ts +124 -0
- package/src/serve/hub-app/prose-hits-view.tsx +176 -0
- package/src/serve/hub-app/prose-hits.css +21 -0
- package/src/serve/hub-app/run-strip.ts +47 -0
- package/src/serve/hub-app/runs-tabs.ts +24 -0
- package/src/serve/hub-app/test-view.tsx +107 -0
- package/src/serve/hub-app/tests-svc.ts +62 -0
- package/src/serve/hub-app/tests-view.tsx +238 -0
- package/src/serve/hub-app/time-ago.tsx +34 -0
- package/src/serve/hub-app/timeline-chart.tsx +274 -0
- package/src/serve/hub-app/timeline-geometry.ts +314 -0
- package/src/serve/hub-app/timeline-svc.ts +62 -0
- package/src/serve/hub-app/timeline-view.tsx +245 -0
- package/src/serve/hub-app/tree-build.ts +410 -0
- package/src/serve/hub-app/tree-row.tsx +42 -0
- package/src/serve/hub-app/tree-svc.ts +174 -0
- package/src/serve/hub-app/wikis-view.tsx +53 -0
- package/src/serve/other-repos.ts +79 -0
- package/src/serve/project-gutter-route.ts +350 -0
- package/src/serve/project-gutter.ts +449 -0
- package/src/serve/run-serve.ts +245 -16
- package/src/serve/same-origin.ts +14 -0
- package/src/serve/serve-hub.ts +213 -18
- package/src/shipkit.ts +19 -12
- package/src/smoke/run-smoke.ts +3 -2
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +216 -18
- package/src/suite/kept-timings.ts +58 -0
- package/src/suite/runner-shapes.ts +88 -0
- package/src/suite/suite-cli.ts +147 -24
- package/src/suite/suite-cpu-preload.ts +92 -0
- package/src/suite/test-units.ts +243 -0
- package/src/ts/config.ts +4 -69
- package/src/ts/jsonc.ts +69 -0
- package/src/ts/lex.ts +143 -0
- package/src/ts/rename.ts +73 -10
- package/src/ts/source-graph.ts +28 -8
- package/src/ts/tsconfig-projects.ts +6 -97
- package/src/ts/tsconfig-resolve.ts +108 -0
- package/src/tui/build-steps-panel.ts +61 -0
- package/src/tui/checks-panel.ts +24 -16
- package/src/tui/run-tui.ts +2 -2
- package/src/tui/tui-panel.ts +5 -4
- package/src/wiki/graph-colors.ts +68 -0
- package/src/wiki/graph-mount.ts +7 -9
- package/src/wiki/graph-page.ts +42 -25
- package/src/wiki/graph-positions.ts +105 -0
- package/src/wiki/run-wiki.ts +1 -1
- package/src/wiki/serve-graph.ts +33 -3
- package/src/cli/cmd_Rules.ts +0 -51
- package/src/cli/cmd_Units.ts +0 -82
- package/src/collections/graphs.ts +0 -32
- package/src/contracts/contracts-once.ts +0 -43
- package/src/core/check-suite.ts +0 -302
- package/src/core/repair-contract-dirs.ts +0 -136
- package/src/core/task-runner.ts +0 -339
- package/src/ledger/gate-units.ts +0 -138
- package/src/ledger/unit-table.ts +0 -105
- package/src/rules/SK002-contracts-fulfillness.ts +0 -24
- package/src/rules/SK003-claude-md-shape.ts +0 -70
- package/src/rules/SK004-gitignore-policy.ts +0 -238
- package/src/rules/SK010-undeclared-release-unit.ts +0 -127
- package/src/rules/rule-registry.ts +0 -64
- package/src/rules/run-fix.ts +0 -72
- package/src/tui/tasks-panel.ts +0 -60
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ONE ROW of the resource tree: a fixed grid of caret, kind, label, duration,
|
|
2
|
+
// note and state glyph. The indent rides in the first column, the label eats
|
|
3
|
+
// the slack, and the glyph is LAST so the wide duration column never pushes it
|
|
4
|
+
// around. No row carries prose: the words are the tooltip and the page it opens.
|
|
5
|
+
import { observer } from 'mobx-react-lite'
|
|
6
|
+
import type { CSSProperties } from 'react'
|
|
7
|
+
import type { HubSt } from './hub-st.ts'
|
|
8
|
+
import { GLYPH, markOf, rowName, rowSlots, type TreeRow } from './tree-build.ts'
|
|
9
|
+
|
|
10
|
+
/** the fold marker: a chevron, right when the branch is shut and down when
|
|
11
|
+
* it is open. The 9px triangle it replaces read as a dot with an opinion */
|
|
12
|
+
export const Caret = ({ open }: { open: boolean }): React.ReactElement => (
|
|
13
|
+
<svg className={open ? 'cvt open' : 'cvt'} viewBox="0 0 24 24" width="11" height="11" aria-hidden="true">
|
|
14
|
+
<path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" strokeWidth="3.4" strokeLinecap="round" strokeLinejoin="round" />
|
|
15
|
+
</svg>
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
export const TreeRowView = observer(({ app, row }: { app: HubSt; row: TreeRow }) => {
|
|
19
|
+
const t = app.tree
|
|
20
|
+
const u = app.checks
|
|
21
|
+
const running = row.checks.some((id) => u.isRunning(id))
|
|
22
|
+
const s = rowSlots(row, running)
|
|
23
|
+
const mark = markOf(row.res, row.dir, row.kind)
|
|
24
|
+
const name = rowName(row)
|
|
25
|
+
return (
|
|
26
|
+
<div className={`trow${t.cursor === row.key ? ' on' : ''}`} style={{ '--d': row.depth } as CSSProperties}
|
|
27
|
+
title={s.hint} onClick={() => t.click(row)}>
|
|
28
|
+
{/* the chevron folds, the rest of the row opens: on a folder with a
|
|
29
|
+
page of its own the two differ, everywhere else both fold */}
|
|
30
|
+
<span className="tw" onClick={row.dir ? (e) => { e.stopPropagation(); t.fold(row) } : undefined}>
|
|
31
|
+
{row.dir ? <Caret open={t.expanded.has(row.key)} /> : null}
|
|
32
|
+
</span>
|
|
33
|
+
<span className={`ty ${mark.hue}`} title={mark.word}>{mark.glyph}</span>
|
|
34
|
+
<span className={`tl ${row.state}`}>{name.tag == null ? null : <span className="tid">{name.tag}</span>}{name.title}</span>
|
|
35
|
+
<span className={`tdur ${s.durTone ?? ''}`}>{s.dur}</span>
|
|
36
|
+
<span className="tnote">
|
|
37
|
+
{s.count == null ? s.note : <>{s.count.reds > 0 ? <b>{s.count.reds}</b> : null} {s.count.total}</>}
|
|
38
|
+
</span>
|
|
39
|
+
<span className={`tg ${s.tone}`}>{GLYPH[s.state]}</span>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// The resource tree, as state: which tab, what is expanded, where the
|
|
2
|
+
// keyboard cursor sits. The trie itself is pure (tree-build.ts) and cached
|
|
3
|
+
// here as computeds. Everything hand tuned persists to localStorage as
|
|
4
|
+
// newline separated paths, text encodable and hand editable, and comes back
|
|
5
|
+
// on reopen.
|
|
6
|
+
import { makeAutoObservable } from 'mobx'
|
|
7
|
+
import type { HubSt } from './hub-st.ts'
|
|
8
|
+
import type { Resource } from './hub-types.ts'
|
|
9
|
+
import { buildPathTree, buildTypeTree, dirKeys, flattenTree, whenCounts, type TreeNode, type TreeRow } from './tree-build.ts'
|
|
10
|
+
|
|
11
|
+
export type TreeTab = 'path' | 'type'
|
|
12
|
+
|
|
13
|
+
const EXPANDED_KEY = 'shipkit.tree.expanded'
|
|
14
|
+
const TAB_KEY = 'shipkit.tree.tab'
|
|
15
|
+
|
|
16
|
+
// every access is guarded: a private window, blocked site data or a stale
|
|
17
|
+
// origin throws on read AND on write, and a tree that cannot remember is
|
|
18
|
+
// still a working tree
|
|
19
|
+
const readStore = (k: string): string => {
|
|
20
|
+
try {
|
|
21
|
+
return localStorage.getItem(k) ?? ''
|
|
22
|
+
} catch {
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const writeStore = (k: string, v: string): void => {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem(k, v)
|
|
29
|
+
} catch {
|
|
30
|
+
/* nothing to do: persistence is a convenience, never state the page needs */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class TreeSvc {
|
|
35
|
+
tab: TreeTab = 'path'
|
|
36
|
+
expanded = new Set<string>()
|
|
37
|
+
/** the row key under the keyboard cursor */
|
|
38
|
+
cursor: string | null = null
|
|
39
|
+
|
|
40
|
+
constructor(private st: HubSt) {
|
|
41
|
+
this.tab = readStore(TAB_KEY) === 'type' ? 'type' : 'path'
|
|
42
|
+
for (const p of readStore(EXPANDED_KEY).split('\n')) if (p !== '') this.expanded.add(p)
|
|
43
|
+
makeAutoObservable<this, 'st'>(this, { st: false })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** the filter and the search apply BEFORE the grouping, so a filtered
|
|
47
|
+
* tree prunes to the branches that still hold something */
|
|
48
|
+
get visible(): Resource[] {
|
|
49
|
+
const f = this.st.filter
|
|
50
|
+
const q = this.st.query.trim().toLowerCase()
|
|
51
|
+
return this.st.cols.resources.filter((r) => {
|
|
52
|
+
// the filter reads the COLOUR, so what `red` shows is what reads
|
|
53
|
+
// red on the tree. An ask is proactive work, so it is in needs
|
|
54
|
+
// and never in red (collection-types stateOf owns that rule)
|
|
55
|
+
if (f === 'red' && r.state !== 'red') return false
|
|
56
|
+
if (f === 'needs' && r.severity == null) return false
|
|
57
|
+
if (q !== '' && !r.title.toLowerCase().includes(q) && !r.path.toLowerCase().includes(q)) return false
|
|
58
|
+
return true
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get nodes(): TreeNode[] {
|
|
63
|
+
return this.tab === 'type' ? buildTypeTree(this.visible) : buildPathTree(this.visible)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get rows(): TreeRow[] {
|
|
67
|
+
// a live search must SHOW its matches: folds yield while it runs
|
|
68
|
+
if (this.st.query.trim() !== '') return flattenTree(this.nodes, new Set(dirKeys(this.nodes, false)))
|
|
69
|
+
return flattenTree(this.nodes, this.expanded)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** the one line above the tree: what runs always and what does not */
|
|
73
|
+
get whenLine(): string {
|
|
74
|
+
const c = whenCounts(this.st.cols.resources)
|
|
75
|
+
const total = c.always + c.release + c.health + c.off
|
|
76
|
+
if (total === 0) return ''
|
|
77
|
+
return [`${c.always} always`, `${c.release} release`, `${c.health} health`, `${c.off} off`].join(' · ')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get redCount(): number {
|
|
81
|
+
return this.st.cols.redCount
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private save(): void {
|
|
85
|
+
writeStore(EXPANDED_KEY, [...this.expanded].join('\n'))
|
|
86
|
+
writeStore(TAB_KEY, this.tab)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setTab(t: TreeTab): void {
|
|
90
|
+
this.tab = t
|
|
91
|
+
this.cursor = null
|
|
92
|
+
this.save()
|
|
93
|
+
}
|
|
94
|
+
toggle(key: string): void {
|
|
95
|
+
this.expanded.has(key) ? this.expanded.delete(key) : this.expanded.add(key)
|
|
96
|
+
this.save()
|
|
97
|
+
}
|
|
98
|
+
expandAll(): void {
|
|
99
|
+
for (const k of dirKeys(this.nodes)) this.expanded.add(k)
|
|
100
|
+
this.save()
|
|
101
|
+
}
|
|
102
|
+
collapseAll(): void {
|
|
103
|
+
this.expanded.clear()
|
|
104
|
+
this.save()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
rowAt(key: string | null): TreeRow | null {
|
|
108
|
+
return this.rows.find((r) => r.key === key) ?? null
|
|
109
|
+
}
|
|
110
|
+
get cur(): TreeRow | null {
|
|
111
|
+
return this.rowAt(this.cursor)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** a row click is both a cursor move and the open: one click, one result.
|
|
115
|
+
* A folder with a page of its own opens the page, and only its chevron
|
|
116
|
+
* folds (fold): the one convention for a row that is both */
|
|
117
|
+
click(row: TreeRow): void {
|
|
118
|
+
this.cursor = row.key
|
|
119
|
+
if (row.page != null) this.st.go(row.page)
|
|
120
|
+
else if (row.dir) this.toggle(row.key)
|
|
121
|
+
else if (row.res != null) this.st.openResource(row.res)
|
|
122
|
+
}
|
|
123
|
+
/** the chevron: fold or unfold, never open */
|
|
124
|
+
fold(row: TreeRow): void {
|
|
125
|
+
this.cursor = row.key
|
|
126
|
+
if (row.dir) this.toggle(row.key)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
move(d: number): void {
|
|
130
|
+
const rows = this.rows
|
|
131
|
+
if (rows.length === 0) return
|
|
132
|
+
const i = rows.findIndex((r) => r.key === this.cursor)
|
|
133
|
+
const next = i === -1 ? (d > 0 ? 0 : rows.length - 1) : Math.min(rows.length - 1, Math.max(0, i + d))
|
|
134
|
+
this.cursor = rows[next]!.key
|
|
135
|
+
}
|
|
136
|
+
/** right descends: a collapsed directory opens, anything else steps down */
|
|
137
|
+
right(): void {
|
|
138
|
+
const r = this.cur
|
|
139
|
+
if (r != null && r.dir && !this.expanded.has(r.key)) this.toggle(r.key)
|
|
140
|
+
else this.move(1)
|
|
141
|
+
}
|
|
142
|
+
/** left ascends: an open directory closes, anything else climbs to its parent */
|
|
143
|
+
left(): void {
|
|
144
|
+
const r = this.cur
|
|
145
|
+
if (r == null) return this.move(-1)
|
|
146
|
+
if (r.dir && this.expanded.has(r.key)) return this.toggle(r.key)
|
|
147
|
+
const rows = this.rows
|
|
148
|
+
const i = rows.findIndex((x) => x.key === r.key)
|
|
149
|
+
for (let n = i - 1; n >= 0; n--) {
|
|
150
|
+
if (rows[n]!.depth < r.depth) {
|
|
151
|
+
this.cursor = rows[n]!.key
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
open(): void {
|
|
157
|
+
const r = this.cur
|
|
158
|
+
if (r == null) return
|
|
159
|
+
if (r.page != null) this.st.go(r.page)
|
|
160
|
+
else if (r.dir) this.toggle(r.key)
|
|
161
|
+
else if (r.res != null) this.st.openResource(r.res)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** true = the key was ours, so the page must not also scroll */
|
|
165
|
+
key(k: string): boolean {
|
|
166
|
+
if (k === 'ArrowDown') this.move(1)
|
|
167
|
+
else if (k === 'ArrowUp') this.move(-1)
|
|
168
|
+
else if (k === 'ArrowRight') this.right()
|
|
169
|
+
else if (k === 'ArrowLeft') this.left()
|
|
170
|
+
else if (k === 'Enter' || k === ' ') this.open()
|
|
171
|
+
else return false
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The wikis page: one tab per mounted markdown wiki, one link graph at a
|
|
2
|
+
// time, embedded from its own /graph/<id> route. A tab is an item of the
|
|
3
|
+
// wikis collection, so the open wiki is in the url like any other page.
|
|
4
|
+
import { observer } from 'mobx-react-lite'
|
|
5
|
+
import type { HubSt } from './hub-st.ts'
|
|
6
|
+
import type { Resource } from './hub-types.ts'
|
|
7
|
+
|
|
8
|
+
/** the collection id, and the address segment of every wiki */
|
|
9
|
+
export const WIKIS = 'wikis'
|
|
10
|
+
|
|
11
|
+
/** `state` colours the tab label the way the tree colours the wiki's row */
|
|
12
|
+
export type WikiTab = { id: string; label: string; state: Resource['state']; on: boolean; go: () => void }
|
|
13
|
+
|
|
14
|
+
const wikisOf = (app: HubSt): Resource[] => app.cols.resources.filter((r) => r.collection === WIKIS)
|
|
15
|
+
|
|
16
|
+
/** the tabs over the page, in the order the server mounted the wikis */
|
|
17
|
+
export function wikiTabs(app: HubSt): WikiTab[] {
|
|
18
|
+
const sel = app.sel
|
|
19
|
+
return wikisOf(app).map((r) => ({
|
|
20
|
+
id: r.id,
|
|
21
|
+
label: r.title,
|
|
22
|
+
state: r.state,
|
|
23
|
+
on: sel?.t === 'item' && sel.col === WIKIS && sel.id === r.id,
|
|
24
|
+
go: () => app.cols.select(WIKIS, r.id),
|
|
25
|
+
}))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** the header link: the first wiki, or the bare section while the first
|
|
29
|
+
* poll has not listed any */
|
|
30
|
+
export function openWikis(app: HubSt): void {
|
|
31
|
+
const first = wikisOf(app)[0]
|
|
32
|
+
if (first != null) app.cols.select(WIKIS, first.id)
|
|
33
|
+
else app.showCol(WIKIS)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const WikisView = observer(({ app, id }: { app: HubSt; id: string }) => {
|
|
37
|
+
const i = app.cols.find(WIKIS, id)
|
|
38
|
+
const tabs = (
|
|
39
|
+
<span className="seg">
|
|
40
|
+
{wikiTabs(app).map((t) => <button key={t.id} className={t.on ? 'on' : ''} onClick={t.go}><span className={`tl ${t.state}`}>{t.label}</span></button>)}
|
|
41
|
+
</span>
|
|
42
|
+
)
|
|
43
|
+
if (i == null) return <div className="graphview"><div className="gvhead">{tabs}</div><div className="ph">this wiki is not mounted here</div></div>
|
|
44
|
+
return (
|
|
45
|
+
<div className="graphview">
|
|
46
|
+
<div className="gvhead">
|
|
47
|
+
{tabs}
|
|
48
|
+
<span className={`gvtext tl ${i.state}`}>{i.text}</span>
|
|
49
|
+
</div>
|
|
50
|
+
{i.href == null ? <div className="ph">this wiki carries no page to embed</div> : <iframe src={i.href} title={i.title} />}
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The OTHER house repos on this disk, as the live strip reads them: their
|
|
2
|
+
// runs in flight from their own run record, read only, and their hub url
|
|
3
|
+
// when one serves. Which repos count is houseRepos (project-gutter.ts), the
|
|
4
|
+
// same discovery the project gutter lists, minus this repo.
|
|
5
|
+
//
|
|
6
|
+
// Cheap on purpose, because the strip asks every second: the list is read at
|
|
7
|
+
// most every LIST_TTL_MS, and a repo's record is opened only when its file or
|
|
8
|
+
// its write ahead log moved since the last read.
|
|
9
|
+
import { existsSync, realpathSync } from 'fs'
|
|
10
|
+
import { homedir } from 'os'
|
|
11
|
+
import type { LiveRow } from '../ledger/live-runs.ts'
|
|
12
|
+
import { readLiveReadonly } from '../ledger/run-log.ts'
|
|
13
|
+
import { houseRepos, hubPortOf, recordOf, recordStamp, type WiredRepo } from './project-gutter.ts'
|
|
14
|
+
|
|
15
|
+
/** how long the list of repos is trusted before the dev root is read again */
|
|
16
|
+
export const LIST_TTL_MS = 30_000
|
|
17
|
+
|
|
18
|
+
const real = (p: string): string => {
|
|
19
|
+
try {
|
|
20
|
+
return realpathSync(p)
|
|
21
|
+
} catch {
|
|
22
|
+
return p
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** the house repos around `root`, its own checkout left out */
|
|
27
|
+
export function otherHouseRepos(root: string, userHome: string = homedir()): WiredRepo[] {
|
|
28
|
+
const { home, repos } = houseRepos(root, userHome)
|
|
29
|
+
const me = real(home)
|
|
30
|
+
return repos.filter((r) => real(r.path) !== me)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** the other repos and their live rows, re-read only when something moved */
|
|
34
|
+
export class OtherRepos {
|
|
35
|
+
private list: WiredRepo[] = []
|
|
36
|
+
private listedAt = -Infinity
|
|
37
|
+
private rows = new Map<string, { stamp: string; rows: LiveRow[] }>()
|
|
38
|
+
|
|
39
|
+
constructor(private root: string, private now: () => number = Date.now, private userHome: string = homedir()) {}
|
|
40
|
+
|
|
41
|
+
repos(): WiredRepo[] {
|
|
42
|
+
if (this.now() - this.listedAt >= LIST_TTL_MS) {
|
|
43
|
+
this.list = otherHouseRepos(this.root, this.userHome)
|
|
44
|
+
this.listedAt = this.now()
|
|
45
|
+
}
|
|
46
|
+
return this.list
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** every repo's live rows, as its record holds them */
|
|
50
|
+
read(): { repo: string; rows: LiveRow[] }[] {
|
|
51
|
+
const out: { repo: string; rows: LiveRow[] }[] = []
|
|
52
|
+
for (const r of this.repos()) {
|
|
53
|
+
const path = recordOf(r.path)
|
|
54
|
+
if (path == null || !existsSync(path)) continue
|
|
55
|
+
const stamp = recordStamp(path)
|
|
56
|
+
let hit = this.rows.get(r.path)
|
|
57
|
+
if (hit == null || hit.stamp !== stamp) {
|
|
58
|
+
hit = { stamp, rows: readLiveReadonly(path) }
|
|
59
|
+
this.rows.set(r.path, hit)
|
|
60
|
+
}
|
|
61
|
+
out.push({ repo: r.name, rows: hit.rows })
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** a repo's hub url when it serves, checked by asking it who it is */
|
|
67
|
+
async hubOf(repo: string): Promise<string | null> {
|
|
68
|
+
const r = this.repos().find((x) => x.name === repo)
|
|
69
|
+
if (r == null) return null
|
|
70
|
+
const url = `http://localhost:${hubPortOf(r.path)}`
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch(`${url}/hub.json`, { signal: AbortSignal.timeout(500) })
|
|
73
|
+
const j = (await res.json()) as { hub?: string; root?: string }
|
|
74
|
+
return j.hub === 'shipkit' && j.root != null && real(j.root) === real(r.path) ? url : null
|
|
75
|
+
} catch {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|