@vzn/vx-history 0.0.0 → 0.0.649

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.
@@ -0,0 +1,97 @@
1
+ // The timings file `file` names: what vx-history learned, as one JSON
2
+ // file a CI cache carries between runners. `{ version, tasks: { id: ms } }`,
3
+ // keys sorted so an unchanged history writes the same bytes.
4
+
5
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
6
+ import path from 'node:path'
7
+ import type { TaskResources } from '@vzn/vx'
8
+
9
+ const VERSION = 1
10
+
11
+ /** The file's durations; none when it is absent, and none, with a warning, when it is not one. */
12
+ export function readTimings(file: string, warn: (m: string) => void): Record<string, number> {
13
+ let text: string
14
+ try {
15
+ text = readFileSync(file, 'utf8')
16
+ } catch (err) {
17
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {}
18
+ warn(`[vx] vx-history: timings file ${file} not read: ${(err as Error).message}`)
19
+ return {}
20
+ }
21
+ let parsed: unknown
22
+ try {
23
+ parsed = JSON.parse(text)
24
+ } catch {
25
+ // quiet: not JSON is not a timings file, which the warning below says
26
+ parsed = undefined
27
+ }
28
+ const tasks = (parsed as { version?: unknown; tasks?: unknown } | undefined)?.tasks
29
+ if (
30
+ (parsed as { version?: unknown } | undefined)?.version !== VERSION ||
31
+ tasks === null ||
32
+ typeof tasks !== 'object' ||
33
+ Array.isArray(tasks)
34
+ ) {
35
+ warn(
36
+ `[vx] vx-history: timings file ${file} ignored: not { "version": ${VERSION}, "tasks": { id: ms } }`,
37
+ )
38
+ return {}
39
+ }
40
+ const out: Record<string, number> = {}
41
+ for (const [id, ms] of Object.entries(tasks)) {
42
+ // Bounded so no chain of them sums past a number: core refuses a
43
+ // weight that is not finite, and the run failed on a hint.
44
+ if (typeof ms === 'number' && ms >= 0 && ms <= Number.MAX_SAFE_INTEGER) out[id] = ms
45
+ }
46
+ return out
47
+ }
48
+
49
+ /**
50
+ * The carried durations with this machine's p50s over them, written whole:
51
+ * a task this run did not see keeps its carried time. Through a temp and a
52
+ * rename, so a reader never sees half a file. Its directory is made: a CI
53
+ * cache that missed restores none.
54
+ */
55
+ export function writeTimings(
56
+ file: string,
57
+ carried: Readonly<Record<string, number>>,
58
+ table: ReadonlyMap<string, TaskResources>,
59
+ ): void {
60
+ const merged: Record<string, number> = { ...carried }
61
+ for (const [id, h] of table) {
62
+ if (h.p50DurationMs !== undefined) merged[id] = Math.round(h.p50DurationMs)
63
+ }
64
+ const tasks: Record<string, number> = {}
65
+ for (const id of Object.keys(merged).sort()) tasks[id] = merged[id]!
66
+ const tmp = `${file}.${process.pid}.tmp`
67
+ mkdirSync(path.dirname(file), { recursive: true })
68
+ try {
69
+ writeFileSync(tmp, `${JSON.stringify({ version: VERSION, tasks }, null, 2)}\n`)
70
+ renameSync(tmp, file)
71
+ } catch (err) {
72
+ rmSync(tmp, { force: true })
73
+ throw err
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Says so when git does not ignore the file it rewrote: a task whose
79
+ * inputs cover it (a root project's `**`) keyed each run's new timings,
80
+ * and missed the run after every write. Asked after the write, off the run.
81
+ */
82
+ export function warnUnignored(file: string, root: string, warn: (m: string) => void): void {
83
+ const rel = path.relative(root, file)
84
+ // `..x.json` is a name inside the root, not a step out of it.
85
+ if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) return
86
+ const p = Bun.spawnSync({
87
+ cmd: ['git', 'check-ignore', '-q', '--', rel],
88
+ cwd: root,
89
+ stdout: 'ignore',
90
+ stderr: 'ignore',
91
+ })
92
+ // 0: ignored; 128: no repository or git refused, which says nothing of it.
93
+ if (p.exitCode !== 1) return
94
+ warn(
95
+ `[vx] vx-history: timings file ${rel} is not git-ignored, so a task whose inputs cover it re-keys on every rewrite — add it to .gitignore`,
96
+ )
97
+ }