Repository navigation
Expand file tree
/
Copy pathindex.ts
More file actions
341 lines (310 loc) · 12.1 KB
/
Copy pathindex.ts
File metadata and controls
341 lines (310 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/**
* Types for the benchmark data published to the `gh-pages` branch.
*
* These are **descriptive**, not prescriptive. The published tree is a frozen
* contract (352 date directories, 2025-08-22 through 2026-09-30 at the time of
* writing) and every shape here was derived by surveying that tree, not from
* documentation. Where the survey found a field to be absent in some
* directories, it is optional here -- even when current runs always emit it.
*
* Changing a type in this file to be stricter than the data will break
* consumers on historical directories. If a field looks like it should be
* required, check the oldest directories first.
*/
// ---------------------------------------------------------------------------
// Enumerations
// ---------------------------------------------------------------------------
/** Benchmark fixtures. Matches `suite/bench list`. */
export type Fixture = "astro" | "babylon" | "large" | "next" | "run" | "svelte" | "vue";
export const FIXTURES = [
"astro",
"babylon",
"large",
"next",
"run",
"svelte",
"vue",
] as const satisfies readonly Fixture[];
/**
* Benchmark variations **the suite currently runs**. Several contain `+`, which
* is legal in a filename but must be encoded when used in a URL path segment.
*
* This is deliberately NOT a validator for the published tree, which can hold
* variations this list does not. The set drifts in both directions and the
* published tree is the side that moves first.
*
* Example: `ci+setup` was added upstream on 2026-09-30 and the nightly
* published 18 files for it that same day, while this workspace was still on a
* base that predated it, so for a while the published tree was AHEAD of this
* list by one variation. It is absent from all 351 earlier date directories.
*
* Code reading the published tree must therefore treat a variation as an
* opaque string and discover the set from the data, the way `apps/data` does.
* Use {@link isVariation} to ask "does this checkout's suite run this", never
* "is this a real entry in the tree".
*/
export type Variation =
| "build"
| "build-cache"
| "cache"
| "cache+lockfile"
| "cache+lockfile+node_modules"
| "cache+node_modules"
| "ci"
| "ci+setup"
| "clean"
| "lockfile"
| "lockfile+node_modules"
| "node_modules"
| "registry-clean"
| "registry-lockfile"
| "run";
export const VARIATIONS = [
"build",
"build-cache",
"cache",
"cache+lockfile",
"cache+lockfile+node_modules",
"cache+node_modules",
"ci",
"ci+setup",
"clean",
"lockfile",
"lockfile+node_modules",
"node_modules",
"registry-clean",
"registry-lockfile",
"run",
] as const satisfies readonly Variation[];
/** Variations that compare registries rather than package managers. */
export type RegistryVariation = Extract<Variation, `registry-${string}`>;
/**
* A `yyyy-mm-dd` directory name.
*
* The template literal catches obvious mistakes on literals while still
* accepting strings built at runtime. Use {@link isDateKey} to validate a
* value that came from outside the program.
*/
export type DateKey = `${number}-${number}-${number}`;
/** Matches the `yyyy-mm-dd` directory names, including a real calendar check. */
export const DATE_KEY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
/**
* Series names appearing in per-package-manager data. Observed across the
* published tree; the set has grown over time, so treat it as the names seen
* so far rather than a closed set.
*/
export type PackageManager =
| "aube"
| "berry"
| "bun"
| "deno"
| "node"
| "npm"
| "nx"
| "pacquet"
| "pnpm"
| "pnpm11"
| "turbo"
| "upm"
| "vlt"
| "vp"
| "yarn"
| "zpm";
/** Series names appearing in `registry-*` data. */
export type RegistryName = "aws" | "cloudsmith" | "github" | "jfrog" | "npm" | "vlt";
/** Either kind of series name. */
export type SeriesName = PackageManager | RegistryName;
// ---------------------------------------------------------------------------
// /{date}/{fixture}-{variation}.json -- hyperfine output
// ---------------------------------------------------------------------------
/**
* One measured command within a benchmark file.
*
* All ten fields are present in every result object in the published archive:
* one field signature across 151,800 result objects spanning all 352 date
* directories, zero exceptions (exhaustively re-checked in review).
*
* `mean`, `stddev`, `median`, `min`, `max`, `user` and `system` are seconds;
* JSON sometimes encodes them as integers, which is still `number`.
*/
export interface BenchmarkResult {
/** The package manager or registry being measured, e.g. `npm`. */
command: string;
mean: number;
stddev: number;
median: number;
min: number;
max: number;
user: number;
system: number;
/** One entry per hyperfine run. */
times: number[];
/** One entry per hyperfine run, parallel to {@link times}. */
exit_codes: number[];
}
/** `/{date}/{fixture}-{variation}.json`. The only top-level key is `results`. */
export interface BenchmarkFile {
results: BenchmarkResult[];
}
// ---------------------------------------------------------------------------
// /{date}/{fixture}-{variation}-package-count.json
// /{date}/{fixture}-{variation}-process-count.json
// ---------------------------------------------------------------------------
/**
* A package-count entry.
*
* `minCount`/`maxCount` appear only where a variation produced a range, so
* they are optional. They are not limited to `registry-*` variations.
*/
export interface PackageCountEntry {
count: number;
minCount?: number;
maxCount?: number;
}
/** A process-count entry. Only ever `{ count }` in the surveyed tree. */
export interface ProcessCountEntry {
count: number;
}
/**
* `/{date}/{fixture}-{variation}-package-count.json`.
*
* Keyed by package manager for ordinary variations and by **registry name**
* for `registry-*` variations. An index signature rather than a closed record
* because the key set has grown over time; lookups are therefore possibly
* `undefined`.
*
* Also optional per (date, fixture, variation) -- see {@link ProcessCount} for
* the two independent reasons a sibling can be absent.
*/
export type PackageCount = { [series: string]: PackageCountEntry | undefined };
/**
* `/{date}/{fixture}-{variation}-process-count.json`.
*
* **Never infer that this file exists.** Both sibling files are independently
* optional per (date, fixture, variation), and they are missing for two
* unrelated reasons:
*
* 1. `registry-*` variations emit **no process-count at all**, ever. Verified
* exhaustively: zero registry process-count files across all 352 date
* directories. They do publish a package-count, but only from 2026-03-31
* onward -- the same date `registryPerPackageCountChartData` first
* appears, so both arrived in one upstream change. Precisely: on and
* after that date every registry benchmark that EXISTS has a
* package-count. That is not the same as every date having one, because
* some dates run no registry variations at all -- 2026-04-14 has zero
* registry benchmarks.
* 2. `build`, `build-cache` and `run` emit **neither** sibling, because they
* measure a script rather than an install. This is a property of the
* variation, not of a particular run: across all 352 date directories,
* none of their benchmark files has a count of either kind.
*
* On 2026-09-30 that is 75 benchmark files, 72 package-counts and 60
* process-counts: the 15 missing process-counts are the 12 `registry-*` plus
* `next-build`, `next-build-cache` and `run-run`, and those same three are the
* only missing package-counts.
*
* Consult `apps/data`'s manifest, which records the three kinds separately
* rather than deriving one from another.
*/
export type ProcessCount = { [series: string]: ProcessCountEntry | undefined };
// ---------------------------------------------------------------------------
// /{date}/versions.json
// ---------------------------------------------------------------------------
/**
* `/{date}/versions.json` -- the tool versions a run measured.
*
* Values are version strings as each tool reports them, so they are not
* uniformly formatted (`node` is reported with a leading `v`). The key set has
* grown as package managers were added, and `latest/` carries no
* `versions.json` at all.
*/
export type Versions = { [tool: string]: string | undefined };
// ---------------------------------------------------------------------------
// /{date}/chart-data.json -- generate-chart.js output
// ---------------------------------------------------------------------------
/**
* One row of chart data: a fixture plus one group of keys per series.
*
* The shape is dynamic by design. For a series `s` a row may carry:
*
* - `s` the mean, in seconds
* - `s_fill` the series colour, as a hex string
* - `s_stddev` the standard deviation, in seconds
* - `s_count` the package count -- only in the `perPackageCount` sections
* - `s_dnf` true when the series did not finish
*
* Which series appear varies per variation, so every lookup is optional.
*/
export interface ChartRow {
fixture: string;
[seriesKey: string]: string | number | boolean | undefined;
}
/** One chart section. All four keys were present in every section surveyed. */
export interface ChartSection {
/** Series name to hex colour. */
colors: { [series: string]: string | undefined };
/** Variation to one row per fixture. */
data: { [variation: string]: ChartRow[] | undefined };
/** Series names this section plots. */
packageManagers: string[];
/** Variations this section plots. */
variations: string[];
}
/**
* `/{date}/chart-data.json`.
*
* **Only `chartData` and `perPackageCountChartData` are present in all 352
* published date directories.** The rest were added over time and are absent
* from older ones:
*
* | first seen | added |
* | ----------- | ------------------------------------------------ |
* | 2025-08-22 | `chartData`, `perPackageCountChartData` |
* | 2025-09-02 | `versions` |
* | 2025-09-08 | `date` |
* | 2026-02-27 | `registryChartData` |
* | 2026-03-11 | `commitSha` |
* | 2026-03-31 | `registryPerPackageCountChartData` |
*
* A consumer that treats any of the optional fields as required will throw on
* roughly the first half of the archive.
*/
export interface ChartData {
chartData: ChartSection;
perPackageCountChartData: ChartSection;
registryChartData?: ChartSection;
registryPerPackageCountChartData?: ChartSection;
date?: DateKey;
commitSha?: string;
versions?: Versions;
}
// ---------------------------------------------------------------------------
// Guards
// ---------------------------------------------------------------------------
const FIXTURE_SET: ReadonlySet<string> = new Set<string>(FIXTURES);
const VARIATION_SET: ReadonlySet<string> = new Set<string>(VARIATIONS);
export const isFixture = (value: unknown): value is Fixture =>
typeof value === "string" && FIXTURE_SET.has(value);
/**
* True when the suite runs this variation today. **Not** a validity check for
* the published tree -- see {@link Variation}.
*/
export const isVariation = (value: unknown): value is Variation =>
typeof value === "string" && VARIATION_SET.has(value);
/**
* True for a `yyyy-mm-dd` string that is also a real calendar date, so
* `2026-02-30` is rejected.
*/
export const isDateKey = (value: unknown): value is DateKey => {
if (typeof value !== "string" || !DATE_KEY_PATTERN.test(value)) return false;
const time = Date.parse(`${value}T00:00:00Z`);
if (Number.isNaN(time)) return false;
return new Date(time).toISOString().startsWith(value);
};
/**
* The basename, without extension, of the benchmark file for a fixture and
* variation. The `+` in some variation names is preserved -- encode it
* yourself if the result becomes a URL path segment.
*/
export const benchmarkFileStem = (fixture: Fixture, variation: Variation): string =>
`${fixture}-${variation}`;