rvlib-shipkit 2.1.1 → 2.3.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 +50 -0
- package/package.json +2 -1
- package/src/agent-time/mask.ts +43 -0
- package/src/agent-time/render.ts +187 -0
- package/src/agent-time/report.ts +335 -0
- package/src/agent-time/shape.ts +271 -0
- package/src/agent-time/signals.ts +84 -0
- package/src/agent-time/store.ts +379 -0
- package/src/agent-time/timeline.ts +218 -0
- package/src/agent-time/turns.ts +135 -0
- package/src/assets/kits/identity-kit.ts +7 -3
- package/src/assets/run-assets.ts +18 -4
- package/src/checks/SK004-gitignore-policy.check.ts +11 -6
- package/src/checks/SK013-one-way-in-policy.check.ts +13 -8
- package/src/cli/cmd_Calls.ts +53 -0
- package/src/cli/cmd_Commands.ts +163 -43
- package/src/cli/cmd_Review.ts +948 -0
- package/src/cli/cmd_Shot.ts +63 -0
- package/src/cli/cmd_Start.ts +8 -4
- package/src/cli/cmd_Time.ts +112 -0
- package/src/cli/cmd_Trailer.ts +361 -0
- package/src/cli/kit/builtins.ts +40 -0
- package/src/cli/kit/calls.ts +177 -0
- package/src/cli/kit/families.ts +67 -0
- package/src/cli/kit/index.ts +8 -0
- package/src/cli/kit/pages-check.ts +36 -0
- package/src/cli/kit/project-cli.ts +73 -0
- package/src/cli/kit/verb.ts +167 -0
- package/src/cli.ts +15 -2
- package/src/collections/collection-types.ts +6 -1
- package/src/collections/providers.ts +3 -0
- package/src/collections/review.ts +70 -0
- package/src/core/layout.ts +12 -2
- package/src/core/migrate-layout.ts +31 -8
- package/src/core/one-way-in-policy.ts +12 -4
- package/src/reflect/prompt-corpus.ts +15 -114
- package/src/reflect/transcript.ts +254 -0
- package/src/review/anim.ts +228 -0
- package/src/review/auto.ts +141 -0
- package/src/review/format.ts +150 -0
- package/src/review/http.ts +23 -0
- package/src/review/marks.ts +43 -0
- package/src/review/media.ts +45 -0
- package/src/review/model.ts +854 -0
- package/src/review/project.ts +110 -0
- package/src/review/role.ts +48 -0
- package/src/review/routes.ts +228 -0
- package/src/review/store.ts +1002 -0
- package/src/review/threads.ts +237 -0
- package/src/review/trailer-desk.ts +385 -0
- package/src/review/trailer-model.ts +183 -0
- package/src/review/trailer-routes.ts +136 -0
- package/src/review/unit.ts +165 -0
- package/src/review/watchers.ts +54 -0
- package/src/scaffold/cli-skeleton.ts +149 -0
- package/src/scaffold/files/agent-index.md +2 -2
- package/src/scaffold/files/tsconfig.json +3 -0
- package/src/scaffold/files/verify-skill.md +1 -1
- package/src/scaffold/project-scaffold.ts +90 -12
- package/src/serve/embed.ts +35 -0
- package/src/serve/hub-app/activity-view.tsx +1 -1
- package/src/serve/hub-app/activity.css +0 -4
- package/src/serve/hub-app/dock-view.tsx +5 -4
- package/src/serve/hub-app/hash-route.ts +28 -1
- package/src/serve/hub-app/header-bar.tsx +3 -1
- package/src/serve/hub-app/home-st.ts +10 -2
- package/src/serve/hub-app/home-strip.tsx +3 -2
- package/src/serve/hub-app/home.html +1 -0
- package/src/serve/hub-app/hub-app.tsx +80 -11
- package/src/serve/hub-app/hub.css +45 -13
- package/src/serve/hub-app/icons.tsx +9 -0
- package/src/serve/hub-app/index.html +1 -0
- package/src/serve/hub-app/insights-view.tsx +4 -3
- package/src/serve/hub-app/keys.ts +14 -10
- package/src/serve/hub-app/layout-st.ts +63 -2
- package/src/serve/hub-app/planned-view.tsx +3 -2
- package/src/serve/hub-app/planned.css +0 -1
- package/src/serve/hub-app/project-list.css +22 -4
- package/src/serve/hub-app/project-list.tsx +41 -8
- package/src/serve/hub-app/projects-svc.ts +4 -0
- package/src/serve/hub-app/repo-sections.ts +15 -3
- package/src/serve/hub-app/repo-st.ts +45 -3
- package/src/serve/hub-app/review/clip-st.ts +208 -0
- package/src/serve/hub-app/review/kind-view.tsx +171 -0
- package/src/serve/hub-app/review/kit.tsx +113 -0
- package/src/serve/hub-app/review/review-answer.tsx +239 -0
- package/src/serve/hub-app/review/review-board.tsx +46 -0
- package/src/serve/hub-app/review/review-list.tsx +156 -0
- package/src/serve/hub-app/review/review-page.tsx +101 -0
- package/src/serve/hub-app/review/review-st.ts +779 -0
- package/src/serve/hub-app/review/review-views.tsx +503 -0
- package/src/serve/hub-app/review/trailer-page.tsx +416 -0
- package/src/serve/hub-app/review/trailer-st.ts +602 -0
- package/src/serve/hub-app/review/trailer-timeline.tsx +207 -0
- package/src/serve/hub-app/review.css +329 -0
- package/src/serve/hub-app/theme-st.ts +56 -0
- package/src/serve/hub-app/tree-build.ts +4 -0
- package/src/serve/hub-app/tree-row.tsx +1 -1
- package/src/serve/hub-app/tree-svc.ts +4 -0
- package/src/serve/hub-runtime.ts +5 -3
- package/src/serve/same-origin.ts +5 -1
- package/src/serve/serve-hub.ts +8 -0
- package/src/shipkit-files.ts +12 -4
- package/src/shot/client.ts +67 -0
- package/src/shot/clip-encoder.ts +146 -0
- package/src/shot/dom-clip.ts +124 -0
- package/src/shot/serve-shot.ts +54 -0
- package/src/shot/shooter.ts +343 -0
- package/src/shot/shot-ask.ts +189 -0
- package/src/shot/shot.shipkit.ts +14 -0
- package/src/smoke/run-smoke.ts +2 -2
- package/src/start-repo.ts +29 -14
- package/src/suite/judge-suites.ts +56 -10
- package/src/suite/select.ts +2 -5
- package/src/suite/suite-cli.ts +3 -2
- package/src/trailer/adapter.ts +78 -0
- package/src/trailer/build.ts +603 -0
- package/src/trailer/ffmpeg.ts +72 -0
- package/src/trailer/footage.ts +173 -0
- package/src/trailer/grammar.ts +1290 -0
- package/src/trailer/graph.ts +508 -0
- package/src/trailer/holes.ts +72 -0
- package/src/trailer/load-unit.ts +31 -0
- package/src/trailer/look.ts +124 -0
- package/src/trailer/passage.ts +129 -0
- package/src/trailer/plan.ts +192 -0
- package/src/trailer/prune.ts +193 -0
- package/src/trailer/score/compose.ts +240 -0
- package/src/trailer/score/dsp.ts +272 -0
- package/src/trailer/score/palette.ts +188 -0
- package/src/trailer/score/pattern.ts +91 -0
- package/src/trailer/score/score.ts +214 -0
- package/src/trailer/score/sound.ts +112 -0
- package/src/trailer/score/synth.ts +361 -0
- package/src/trailer/states.ts +76 -0
- package/src/trailer/stitch.ts +233 -0
- package/src/trailer/titles.ts +379 -0
- package/src/trailer/url.ts +108 -0
- package/src/trailer/vertical-draw.ts +286 -0
- package/src/trailer/vertical.ts +148 -0
- package/src/trailer/vocab.ts +59 -0
- package/src/trailer/words.ts +254 -0
package/src/cli/cmd_Commands.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// the binary describing itself, in markdown. a hand-copied command list
|
|
2
2
|
// drifts the moment a flag changes, and then teaches commands that do not
|
|
3
|
-
// exist. rendering the live registry makes the doc a build artifact.
|
|
3
|
+
// exist. rendering the live registry makes the doc a build artifact. One
|
|
4
|
+
// renderer for every binary: shipkit's own page and each project CLI's pages
|
|
5
|
+
// (the kit in cli/kit/) come out of these functions.
|
|
4
6
|
import { Command, Option } from 'clipanion'
|
|
5
7
|
import { spliceMarkedBlock } from '../core/doc-block.ts'
|
|
8
|
+
import { type Essential, type Film, marksOf, ROW_OF, runText } from './kit/verb.ts'
|
|
6
9
|
|
|
7
10
|
export type CommandOption = { definition: string; description: string }
|
|
8
11
|
export type CommandRow = {
|
|
@@ -12,6 +15,15 @@ export type CommandRow = {
|
|
|
12
15
|
usage: string
|
|
13
16
|
options: CommandOption[]
|
|
14
17
|
examples: [string, string][]
|
|
18
|
+
/** a filmable command (cli/kit/verb.ts): takes --out, a clip also --seconds */
|
|
19
|
+
film?: Film
|
|
20
|
+
/** a filmable command's take version and length (build/trailer.md, Film lines) */
|
|
21
|
+
version?: number | string
|
|
22
|
+
length?: 'own'
|
|
23
|
+
/** listed on the essentials page */
|
|
24
|
+
essential?: Essential
|
|
25
|
+
/** what a kit row runs */
|
|
26
|
+
runs?: string
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
export type Definition = {
|
|
@@ -30,68 +42,148 @@ export const DESCRIPTION_MAX = 100
|
|
|
30
42
|
// clipanion renders a value placeholder as `#0`, which nobody can type
|
|
31
43
|
const clean = (s: string | null | undefined): string => (s ?? '').trim().replace(/\s+/g, ' ').replace(/ #\d+\b/g, ' <value>')
|
|
32
44
|
|
|
45
|
+
/** the names a binary answers to: shipkit is also `sk` */
|
|
46
|
+
const namesOf = (bin: string): string[] => (bin === 'sk' || bin === 'shipkit' ? ['shipkit', 'sk'] : [bin])
|
|
47
|
+
|
|
48
|
+
const stripName = (bin: string, path: string): string => {
|
|
49
|
+
for (const n of namesOf(bin)) if (path.startsWith(`${n} `)) return path.slice(n.length + 1)
|
|
50
|
+
return path
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
/** rows from the live registry, builtins dropped, sorted by category then path.
|
|
34
54
|
* clipanion splits what we need across two calls: the plain form carries the
|
|
35
55
|
* examples, the detailed form carries the flags. Merge by path, or the page
|
|
36
56
|
* ends up showing a command with no arguments, which is the state that sends
|
|
37
|
-
* an agent to `--help` before it can act.
|
|
38
|
-
|
|
57
|
+
* an agent to `--help` before it can act. `classes` (the registered command
|
|
58
|
+
* classes) add each command's marks: film, essential, what a row runs */
|
|
59
|
+
export function commandRows(plain: Definition[], detailed: Definition[] = [], bin = 'sk', classes: readonly { paths?: string[][] }[] = []): CommandRow[] {
|
|
39
60
|
const flagsByPath = new Map(detailed.map((d) => [d.path, d.options ?? []]))
|
|
61
|
+
const classByPath = new Map<string, object>()
|
|
62
|
+
for (const c of classes) for (const p of c.paths ?? []) classByPath.set(p.join(' '), c)
|
|
40
63
|
return plain
|
|
41
|
-
.filter((d) => !/^(
|
|
42
|
-
.map((d) =>
|
|
43
|
-
path
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
.filter((d) => !/^(--help|-h|--version|-v)$/.test(stripName(bin, d.path)))
|
|
65
|
+
.map((d) => {
|
|
66
|
+
const path = stripName(bin, d.path)
|
|
67
|
+
const c = classByPath.get(path)
|
|
68
|
+
const row: CommandRow = {
|
|
69
|
+
path,
|
|
70
|
+
category: clean(d.category) || 'Other',
|
|
71
|
+
// verbatim from the CLI so the doc cannot say something it does not
|
|
72
|
+
description: clean(d.description),
|
|
73
|
+
usage: clean(d.usage),
|
|
74
|
+
options: (flagsByPath.get(d.path) ?? [])
|
|
75
|
+
.map((o) => ({ definition: clean(o.definition), description: clean(o.description) }))
|
|
76
|
+
.filter((o) => o.definition !== ''),
|
|
77
|
+
examples: (d.examples ?? []).map(([label, cmd]) => [clean(label), clean(cmd)] as [string, string]),
|
|
78
|
+
}
|
|
79
|
+
if (c != null) {
|
|
80
|
+
const m = marksOf(c)
|
|
81
|
+
if (m.film != null) row.film = m.film
|
|
82
|
+
if (m.version != null) row.version = m.version
|
|
83
|
+
if (m.length != null) row.length = m.length
|
|
84
|
+
if (m.essential != null) row.essential = m.essential
|
|
85
|
+
const v = ROW_OF.get(c)
|
|
86
|
+
if (v != null) row.runs = runText(v.run)
|
|
87
|
+
}
|
|
88
|
+
return row
|
|
89
|
+
})
|
|
53
90
|
.sort((a, b) => a.category.localeCompare(b.category) || a.path.localeCompare(b.path))
|
|
54
91
|
}
|
|
55
92
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
93
|
+
/** a command's invocation as an agent types it */
|
|
94
|
+
const signature = (r: CommandRow, bin: string): string => r.usage.replace(/^\$0/, bin).trim() || `${bin} ${r.path}`
|
|
95
|
+
|
|
96
|
+
/** the markdown block: per command its invocation with every flag, one line
|
|
97
|
+
* of description, what a row runs, then its examples. What a flag does and
|
|
98
|
+
* the long text are `<bin> <command> --help`. */
|
|
99
|
+
export function renderMarkdown(rows: CommandRow[], bin = 'sk'): string {
|
|
60
100
|
const out: string[] = []
|
|
61
101
|
let cat = ''
|
|
62
102
|
for (const r of rows) {
|
|
63
103
|
if (r.category !== cat) { cat = r.category; out.push(`\n### ${cat}\n`) }
|
|
64
|
-
const sig = r.usage.replace(/^\$0/, 'sk').trim() || `sk ${r.path}`
|
|
65
104
|
const flags = r.options.map((o) => `[${o.definition}]`).join(' ')
|
|
105
|
+
const sig = signature(r, bin)
|
|
66
106
|
// one line per bullet: a description on its own indented line reads as
|
|
67
107
|
// hard-wrapped prose to the house lint
|
|
68
|
-
out.push(`- \`${sig}${flags === '' ? '' : ` ${flags}`}\` — ${r.description}`)
|
|
108
|
+
out.push(`- \`${sig}${flags === '' ? '' : ` ${flags}`}\` — ${r.description}${r.runs != null ? ` → \`${r.runs}\`` : ''}${r.film != null ? ` (films a ${r.film})` : ''}`)
|
|
69
109
|
for (const [label, cmd] of r.examples) out.push(` - ${label}: \`${cmd}\``)
|
|
70
110
|
}
|
|
71
111
|
return out.join('\n').trim()
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
/** the eager form: one line per command, its positional arguments and its
|
|
75
|
-
* description. Flags and examples are
|
|
115
|
+
* description. Flags and examples are `<bin> <command> --help`, so a page every
|
|
76
116
|
* session loads costs a line per command, not a screen */
|
|
77
|
-
export function renderCompact(rows: CommandRow[]): string {
|
|
117
|
+
export function renderCompact(rows: CommandRow[], bin = 'sk'): string {
|
|
78
118
|
const out: string[] = []
|
|
79
119
|
let cat = ''
|
|
80
120
|
for (const r of rows) {
|
|
81
121
|
if (r.category !== cat) { cat = r.category; out.push(`\n### ${cat}\n`) }
|
|
82
|
-
const sig = r.usage.replace(/^\$0/,
|
|
122
|
+
const sig = r.usage.replace(/^\$0/, bin).replace(/\s+\[(?:-|<\w+>)[^\]]*\]/g, '').trim() || `${bin} ${r.path}`
|
|
83
123
|
out.push(`- \`${sig}\` — ${r.description}`)
|
|
84
124
|
}
|
|
85
125
|
return out.join('\n').trim()
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
/** the page every session loads: per category its everyday commands, a few
|
|
129
|
+
* examples, and how to list the rest. Generated whole */
|
|
130
|
+
export function renderEssentials(rows: CommandRow[], bin: string, command: string): string {
|
|
131
|
+
const out = [`# ${bin}: the everyday commands`, '', `Generated by \`${command}\` from the commands marked \`essential\`. Every command: \`${bin}\`, \`${bin} <family>\`, \`${bin} <command> --help\`. \`${bin} <words>\` runs as \`bun run cli <words>\` from the repo root, and as \`${bin}\` anywhere once \`bun link\` ran there.`]
|
|
132
|
+
const first = (r: CommandRow): string => r.path.split(' ')[0]!
|
|
133
|
+
const quiet: string[] = []
|
|
134
|
+
for (const category of [...new Set(rows.map((r) => r.category))]) {
|
|
135
|
+
const all = rows.filter((r) => r.category === category)
|
|
136
|
+
const rest = [...new Set(all.map(first))]
|
|
137
|
+
.map((w) => [w, all.filter((r) => first(r) === w && r.essential == null).length] as const)
|
|
138
|
+
.filter(([, n]) => n > 0)
|
|
139
|
+
.map(([w, n]) => `\`${bin} ${w}${all.some((r) => r.path === w) ? ' --help' : ''}\` (${n} command${n === 1 ? '' : 's'})`)
|
|
140
|
+
const mine = all.filter((r) => r.essential != null)
|
|
141
|
+
if (mine.length === 0) { quiet.push(...rest); continue }
|
|
142
|
+
out.push('', `## ${category}${rest.length > 0 ? ` (more: ${rest.join(', ')})` : ''}`, '')
|
|
143
|
+
for (const r of mine) {
|
|
144
|
+
const e = r.essential!
|
|
145
|
+
out.push(`- \`${bin} ${r.path}${e.args != null ? ` ${e.args}` : ''}\`: ${r.description}`)
|
|
146
|
+
for (const [label, cmd] of r.examples.slice(0, e.examples ?? 0)) out.push(` - \`${cmd}\`: ${label}`)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (quiet.length > 0) out.push('', `## The other families: ${quiet.join(', ')}`)
|
|
150
|
+
return `${out.join('\n')}\n`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** what a project CLI's registry must hold for its pages and its listing to
|
|
154
|
+
* teach right: a category, a description under the cap, and an example that
|
|
155
|
+
* starts with the command's own words. One line per breach */
|
|
156
|
+
export function shapeProblems(rows: CommandRow[], bin: string): string[] {
|
|
157
|
+
const out: string[] = []
|
|
158
|
+
for (const r of rows) {
|
|
159
|
+
if (r.category === 'Other') out.push(`${bin} ${r.path}: no category`)
|
|
160
|
+
if (r.description === '') out.push(`${bin} ${r.path}: no description`)
|
|
161
|
+
else if (r.description.length > DESCRIPTION_MAX) out.push(`${bin} ${r.path}: description over ${DESCRIPTION_MAX} characters (the rest goes in details)`)
|
|
162
|
+
if (!r.examples.some(([, cmd]) => cmd === `${bin} ${r.path}` || cmd.startsWith(`${bin} ${r.path} `))) out.push(`${bin} ${r.path}: no example starting with \`${bin} ${r.path}\``)
|
|
163
|
+
}
|
|
164
|
+
return out
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** the markers a page carries around the generated block: shipkit keeps its
|
|
168
|
+
* historical name, a project CLI uses its own */
|
|
169
|
+
export const markersFor = (bin: string): [string, string] => {
|
|
170
|
+
const n = namesOf(bin).includes('shipkit') ? 'shipkit' : bin
|
|
171
|
+
return [`<!-- ${n}:commands -->`, `<!-- /${n}:commands -->`]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const [BEGIN, END] = markersFor('sk')
|
|
90
175
|
|
|
91
176
|
/** replace the marked block in a host file. Returns null when the markers are
|
|
92
177
|
* missing, so a renamed or hand-mangled host fails loud instead of silently
|
|
93
178
|
* writing nothing. */
|
|
94
|
-
export const spliceBlock = (host: string, block: string): string | null => spliceMarkedBlock(host, block,
|
|
179
|
+
export const spliceBlock = (host: string, block: string, bin = 'sk'): string | null => spliceMarkedBlock(host, block, ...markersFor(bin))
|
|
180
|
+
|
|
181
|
+
/** the page a project CLI's `--out` creates when it is missing: a title, how
|
|
182
|
+
* it is made, and the markers the block goes between */
|
|
183
|
+
export const newCommandsPage = (bin: string, path: string): string => {
|
|
184
|
+
const [begin, end] = markersFor(bin)
|
|
185
|
+
return `# ${bin}: every command\n\nGenerated between the markers by \`${bin} commands --out ${path}\` from the registry. \`${bin}\` lists every command, \`${bin} <command> --help\` prints one.\n\n${begin}\n${end}\n`
|
|
186
|
+
}
|
|
95
187
|
|
|
96
188
|
export class CommandsCommand extends Command {
|
|
97
189
|
static override paths = [['commands']]
|
|
@@ -109,33 +201,61 @@ export class CommandsCommand extends Command {
|
|
|
109
201
|
],
|
|
110
202
|
})
|
|
111
203
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
204
|
+
/** a project CLI holds its rows to shapeProblems (--out and --check fail on
|
|
205
|
+
* a breach), and its --out creates a missing page */
|
|
206
|
+
static shapeRule = false
|
|
207
|
+
|
|
208
|
+
json = Option.Boolean('--json', false, { description: 'machine-readable rows, with film and essential where a command carries them' })
|
|
209
|
+
out = Option.String('--out', { description: 'host file carrying the <bin>:commands markers. The block between them is rewritten' })
|
|
210
|
+
check = Option.Boolean('--check', false, { description: 'with --out or --essentials: exit 1 if a page is stale, write nothing' })
|
|
211
|
+
compact = Option.Boolean('--compact', false, { description: 'one line per command: no flags, no examples (they are `<bin> <command> --help`)' })
|
|
212
|
+
essentials = Option.String('--essentials', { description: 'the everyday commands page, rewritten whole from the commands marked essential' })
|
|
116
213
|
|
|
117
214
|
async execute(): Promise<number> {
|
|
215
|
+
const bin = this.cli.binaryName
|
|
216
|
+
const classes = (this.context as { classes?: { paths?: string[][] }[] }).classes ?? []
|
|
118
217
|
const rows = commandRows(
|
|
119
218
|
this.cli.definitions() as Definition[],
|
|
120
219
|
(this.cli.definitions as (o?: { detailed?: boolean }) => Definition[])({ detailed: true }),
|
|
220
|
+
bin,
|
|
221
|
+
classes,
|
|
121
222
|
)
|
|
122
|
-
|
|
123
|
-
|
|
223
|
+
const write = (s: string): void => void this.context.stdout.write(s)
|
|
224
|
+
if (this.out == null && this.essentials == null) {
|
|
225
|
+
write(this.json ? `${JSON.stringify(rows)}\n` : `${(this.compact ? renderCompact : renderMarkdown)(rows, bin)}\n`)
|
|
124
226
|
return 0
|
|
125
227
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
228
|
+
if ((this.constructor as typeof CommandsCommand).shapeRule) {
|
|
229
|
+
const bad = shapeProblems(rows, bin)
|
|
230
|
+
for (const b of bad) write(`FAIL: ${b}\n`)
|
|
231
|
+
if (bad.length > 0) { write(' fix: give the row a category, a short description and an example that starts with its words\n'); return 1 }
|
|
232
|
+
}
|
|
233
|
+
const { existsSync, mkdirSync, readFileSync, writeFileSync: writeRaw } = await import('fs')
|
|
234
|
+
const { dirname } = await import('path')
|
|
235
|
+
const writeFileSync = (path: string, text: string): void => { mkdirSync(dirname(path), { recursive: true }); writeRaw(path, text) }
|
|
236
|
+
let code = 0
|
|
237
|
+
if (this.essentials != null) {
|
|
238
|
+
const cmd = `${bin} commands${this.out != null ? ` --out ${this.out}` : ''} --essentials ${this.essentials}`
|
|
239
|
+
const text = renderEssentials(rows, bin, cmd)
|
|
240
|
+
if (existsSync(this.essentials) && readFileSync(this.essentials, 'utf8') === text) write(`ok: ${this.essentials} current\n`)
|
|
241
|
+
else if (this.check) { write(`FAIL: ${this.essentials} is STALE — the essential commands moved\n fix: ${cmd}\n`); code = 1 }
|
|
242
|
+
else { writeFileSync(this.essentials, text); write(`ok: ${this.essentials} rewritten\n`) }
|
|
243
|
+
}
|
|
244
|
+
if (this.out == null) return code
|
|
245
|
+
const [begin, end] = markersFor(bin)
|
|
246
|
+
const project = (this.constructor as typeof CommandsCommand).shapeRule
|
|
247
|
+
if (!existsSync(this.out) && (!project || this.check)) { write(`FAIL: ${this.out} does not exist\n`); return 1 }
|
|
248
|
+
const host = existsSync(this.out) ? readFileSync(this.out, 'utf8') : newCommandsPage(bin, this.out)
|
|
249
|
+
const next = spliceBlock(host, (this.compact ? renderCompact : renderMarkdown)(rows, bin), bin)
|
|
250
|
+
if (next == null) { write(`FAIL: ${this.out} has no ${begin} / ${end} markers\n`); return 1 }
|
|
251
|
+
if (next === host) { write(`ok: ${this.out} command index current (${rows.length} commands)\n`); return code }
|
|
132
252
|
if (this.check) {
|
|
133
|
-
|
|
134
|
-
|
|
253
|
+
write(`FAIL: ${this.out} command index is STALE — the binary registers commands the page does not list\n`)
|
|
254
|
+
write(` fix: ${bin === 'sk' ? 'shipkit' : bin} commands${this.compact ? ' --compact' : ''} --out ${this.out}\n`)
|
|
135
255
|
return 1
|
|
136
256
|
}
|
|
137
257
|
writeFileSync(this.out, next)
|
|
138
|
-
|
|
139
|
-
return
|
|
258
|
+
write(`ok: ${this.out} command index rewritten (${rows.length} commands)\n`)
|
|
259
|
+
return code
|
|
140
260
|
}
|
|
141
261
|
}
|