rvlib-shipkit 2.1.0 → 2.2.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 +18 -0
- package/package.json +2 -1
- 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_CacheAudit.ts +3 -2
- package/src/cli/cmd_Calls.ts +53 -0
- package/src/cli/cmd_Ci.ts +4 -3
- package/src/cli/cmd_Commands.ts +158 -43
- package/src/cli/cmd_Fleet.ts +3 -1
- package/src/cli/cmd_GlobalProcesses.ts +7 -2
- package/src/cli/cmd_Shot.ts +63 -0
- package/src/cli/cmd_Start.ts +8 -4
- package/src/cli/cmd_Trailer.ts +108 -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 +157 -0
- package/src/cli/list-flag.ts +3 -0
- package/src/cli.ts +13 -2
- package/src/core/layout.ts +10 -2
- package/src/core/migrate-layout.ts +31 -8
- package/src/core/one-way-in-policy.ts +12 -4
- package/src/mv/mv-md.ts +2 -1
- package/src/reflect/prompt-corpus.ts +5 -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/shipkit-files.ts +8 -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 +23 -0
- package/src/trailer/build.ts +344 -0
- package/src/trailer/ffmpeg.ts +50 -0
- package/src/trailer/footage.ts +104 -0
- package/src/trailer/grammar.ts +915 -0
- package/src/trailer/graph.ts +323 -0
- package/src/trailer/load-unit.ts +14 -0
- package/src/trailer/look.ts +110 -0
- package/src/trailer/plan.ts +133 -0
- package/src/trailer/stitch.ts +150 -0
- package/src/trailer/titles.ts +274 -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 +40 -0
- package/src/ts/rename.ts +9 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// the takes on disk: each film line that writes its own take (a project CLI film row, a `film run`, a `film url`) filmed once into
|
|
2
|
+
// `.shipkit/cache/trailer/footage/<key>.<ext>`, a stamp beside it naming the world it was filmed on. A take of an older
|
|
3
|
+
// world is reported, and filmed again only on --refilm. Written aside then renamed, two at once
|
|
4
|
+
import { spawnSync } from 'node:child_process'
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { duration } from './ffmpeg.ts'
|
|
8
|
+
import type { Trailer } from './grammar.ts'
|
|
9
|
+
import { cliArgv, isTake, runCommand, type Stamp, type Take, type TakeFilm, takeIsStill, takeName, takeSeconds } from './plan.ts'
|
|
10
|
+
|
|
11
|
+
export const trailerCache = (root: string, part: 'footage' | 'pieces' | 'titles' | 'sound'): string => join(root, '.shipkit', 'cache', 'trailer', part)
|
|
12
|
+
|
|
13
|
+
/** every shot's take, with its stamp when it was filmed */
|
|
14
|
+
export function takesOf(t: Trailer, root: string): Take[] {
|
|
15
|
+
const dir = trailerCache(root, 'footage')
|
|
16
|
+
return t.shots.flatMap((shot) => {
|
|
17
|
+
const film = shot.film
|
|
18
|
+
if (!isTake(film)) return []
|
|
19
|
+
const file = join(dir, takeName(shot, film, t))
|
|
20
|
+
const stampFile = `${file}.json`
|
|
21
|
+
const stamp = existsSync(file) && existsSync(stampFile) ? (JSON.parse(readFileSync(stampFile, 'utf8')) as Stamp) : null
|
|
22
|
+
return [{ shot, film, file, stamp }]
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const git = (root: string, ...args: string[]): string | null => {
|
|
27
|
+
const r = spawnSync('git', args, { cwd: root, encoding: 'utf8' })
|
|
28
|
+
return r.status === 0 ? r.stdout : null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** the world the takes show: the repo's committed tree and what the working tree changes, trailer folders and the
|
|
32
|
+
* shipkit cache left out; a repo with no git: `no-git` */
|
|
33
|
+
export function worldStamp(root: string): string {
|
|
34
|
+
const tree = git(root, 'rev-parse', 'HEAD^{tree}')
|
|
35
|
+
if (tree === null) return 'no-git'
|
|
36
|
+
const h = new Bun.CryptoHasher('sha1').update(tree)
|
|
37
|
+
const dirty = (git(root, 'status', '--porcelain=v1', '-z', '--untracked-files=all', '--', '.', ':(exclude)trailer', ':(exclude).shipkit') ?? '')
|
|
38
|
+
.split('\0')
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((l) => l.slice(3))
|
|
41
|
+
.sort()
|
|
42
|
+
for (const f of dirty) {
|
|
43
|
+
h.update(f)
|
|
44
|
+
const abs = join(root, f)
|
|
45
|
+
try {
|
|
46
|
+
h.update(readFileSync(abs))
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
return h.digest('hex').slice(0, 12)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface FilmContext {
|
|
53
|
+
root: string
|
|
54
|
+
t: Pick<Trailer, 'size' | 'fps' | 'base'>
|
|
55
|
+
world: string
|
|
56
|
+
/** the project CLI's argv, null when the repo has none */
|
|
57
|
+
cli: readonly string[] | null
|
|
58
|
+
say: (line: string) => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** film one take; a failure throws with the shot, its line and what the command said */
|
|
62
|
+
export async function filmTake(tk: Take, c: FilmContext): Promise<Take> {
|
|
63
|
+
mkdirSync(trailerCache(c.root, 'footage'), { recursive: true })
|
|
64
|
+
const part = tk.file.replace(/(\.\w+)$/, '.part$1')
|
|
65
|
+
rmSync(part, { force: true })
|
|
66
|
+
const t0 = performance.now()
|
|
67
|
+
const seconds = takeSeconds(tk.shot, tk.film)
|
|
68
|
+
const f = tk.film
|
|
69
|
+
const said = f.kind === 'url' ? await urlTake(tk, f, part, seconds, c) : await commandTake(tk, f, part, seconds, c)
|
|
70
|
+
renameSync(part, tk.file)
|
|
71
|
+
const still = takeIsStill(f)
|
|
72
|
+
const stamp: Stamp = { world: c.world, filmedAt: new Date().toISOString(), seconds: still ? 0 : duration(tk.file), note: said.split('\n')[0]?.slice(0, 200) ?? '' }
|
|
73
|
+
writeFileSync(`${tk.file}.json`, JSON.stringify(stamp, null, 2))
|
|
74
|
+
c.say(` filmed ${tk.shot.name}: ${still ? 'a still' : `${stamp.seconds.toFixed(2)} s`} in ${((performance.now() - t0) / 1000).toFixed(1)} s`)
|
|
75
|
+
return { ...tk, stamp }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** a take a command writes at `part` (a `film run`, a project CLI row); what it said, or a throw with its last lines */
|
|
79
|
+
async function commandTake(tk: Take, f: Exclude<TakeFilm, { kind: 'url' }>, part: string, seconds: number | null, c: FilmContext): Promise<string> {
|
|
80
|
+
if (f.kind === 'cli' && c.cli === null) throw new Error(`shot ${tk.shot.name} (line ${f.line}): film ${f.argv.join(' ')} needs the project CLI (the cli script of package.json)`)
|
|
81
|
+
const argv = f.kind === 'run' ? ['sh', '-c', runCommand(f, { out: part, seconds, size: c.t.size, fps: c.t.fps })] : cliArgv(c.cli!, f, part, seconds)
|
|
82
|
+
const p = Bun.spawn(argv, { cwd: c.root, stdout: 'pipe', stderr: 'pipe' })
|
|
83
|
+
const [out, err, code] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text(), p.exited])
|
|
84
|
+
const said = `${out}${err}`.trim()
|
|
85
|
+
if (code !== 0 || !existsSync(part)) {
|
|
86
|
+
rmSync(part, { force: true })
|
|
87
|
+
const shown = f.kind === 'run' ? f.cmd : [...(c.cli ?? []), ...f.argv].join(' ')
|
|
88
|
+
throw new Error(`shot ${tk.shot.name} (line ${f.line}): ${shown} ${code !== 0 ? `exited ${code}` : `wrote no file at its --out`}${said ? `\n${said.split('\n').slice(-8).join('\n')}` : ''}`)
|
|
89
|
+
}
|
|
90
|
+
return said
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** a `film url` take through the house shot service; a throw names the shot, its line and the service's reason */
|
|
94
|
+
async function urlTake(tk: Take, f: Extract<TakeFilm, { kind: 'url' }>, part: string, seconds: number | null, c: FilmContext): Promise<string> {
|
|
95
|
+
const { filmUrl } = await import('./url.ts')
|
|
96
|
+
try {
|
|
97
|
+
const note = await filmUrl(f, { root: c.root, base: c.t.base, size: c.t.size, fps: c.t.fps, seconds, out: part, say: c.say })
|
|
98
|
+
if (!existsSync(part)) throw new Error('the shot service wrote no file')
|
|
99
|
+
return note
|
|
100
|
+
} catch (e) {
|
|
101
|
+
rmSync(part, { force: true })
|
|
102
|
+
throw new Error(`shot ${tk.shot.name} (line ${f.line}): film url ${f.route}: ${(e as Error).message}`)
|
|
103
|
+
}
|
|
104
|
+
}
|