@vzn/vx 0.0.520 → 0.0.522

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.520",
3
+ "version": "0.0.522",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.520",
25
- "@vzn/vx-linux-arm64": "0.0.520",
26
- "@vzn/vx-darwin-x64": "0.0.520",
27
- "@vzn/vx-darwin-arm64": "0.0.520"
24
+ "@vzn/vx-linux-x64": "0.0.522",
25
+ "@vzn/vx-linux-arm64": "0.0.522",
26
+ "@vzn/vx-darwin-x64": "0.0.522",
27
+ "@vzn/vx-darwin-arm64": "0.0.522"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -214,6 +214,22 @@ export class GitFilesCache extends Map<string, readonly string[]> {
214
214
  this.dirty = dirty
215
215
  }
216
216
 
217
+ /** The populate's `git var -l`, for `configValue`. */
218
+ private vars: Map<string, string> | null = null
219
+
220
+ setGitVars(listing: string | null): void {
221
+ this.vars = listing === null ? null : gitVarList(listing)
222
+ }
223
+
224
+ /**
225
+ * A config key's value as `git config --get` answers it (the last one),
226
+ * from the populate's `git var -l`: null when the config has none,
227
+ * undefined when no listing was read.
228
+ */
229
+ configValue(key: string): string | null | undefined {
230
+ return this.vars === null ? undefined : (this.vars.get(key) ?? null)
231
+ }
232
+
217
233
  markOutputsChanged(projectDir: string, relPaths: readonly string[]): void {
218
234
  this.recordChanged(projectDir, relPaths)
219
235
  // The workspace-wide partition sees the same files under
@@ -774,6 +790,12 @@ async function dropFilteredOids(
774
790
  pathspecs: readonly string[]
775
791
  gitVars: string
776
792
  spawnGit: (a: string[], stdin?: string) => Promise<GitRun | null>
793
+ /**
794
+ * The same `check-attr`, already started over every listed index entry
795
+ * (a superset of `trusted`) because `attributesInIndex` fired before
796
+ * `status` was back.
797
+ */
798
+ early?: Promise<GitRun | null> | undefined
777
799
  },
778
800
  ): Promise<void> {
779
801
  if (trusted.size === 0) return
@@ -823,14 +845,48 @@ async function dropFilteredOids(
823
845
  // blob's OID keyed an edit git calls clean, and the run replayed the old
824
846
  // output (item 978). Git LFS is a `filter` as well: its files are hashed
825
847
  // from disk now, the one honest identity of what a task reads.
826
- const res = await args.spawnGit(
827
- ['check-attr', '--stdin', '-z', 'text', 'eol', 'ident', 'filter', 'working-tree-encoding'],
828
- [...trusted.keys()].join('\0'),
829
- )
848
+ const res = await (args.early ?? args.spawnGit(CHECK_ATTR, [...trusted.keys()].join('\0')))
830
849
  if (res === null || res.exitCode !== 0) return
831
850
  for (const rel of parseCheckAttrOutput(res.stdout)) trusted.delete(rel)
832
851
  }
833
852
 
853
+ const CHECK_ATTR = [
854
+ 'check-attr',
855
+ '--stdin',
856
+ '-z',
857
+ 'text',
858
+ 'eol',
859
+ 'ident',
860
+ 'filter',
861
+ 'working-tree-encoding',
862
+ ]
863
+
864
+ /**
865
+ * The filter gate's half that needs only the index listing: an attributes
866
+ * file tracked in it, in `$GIT_DIR/info/attributes`, or above the scanned
867
+ * dirs. When it fires, `dropFilteredOids` is sure to ask `check-attr`, so the
868
+ * enumeration starts it beside `status` (~8 ms on this repo, U-3)
869
+ * instead of after it. When it does not, the whole gate decides later.
870
+ */
871
+ function attributesInIndex(args: {
872
+ tracked: readonly string[]
873
+ workspaceRoot: string
874
+ gitDir: string
875
+ gitPrefix: string
876
+ pathspecs: readonly string[]
877
+ }): boolean {
878
+ if (
879
+ args.gitDir !== '' &&
880
+ existsSync(path.resolve(args.workspaceRoot, args.gitDir, 'info', 'attributes'))
881
+ ) {
882
+ return true
883
+ }
884
+ for (const rel of args.tracked) {
885
+ if (rel === '.gitattributes' || rel.endsWith('/.gitattributes')) return true
886
+ }
887
+ return attributesAbove(args)
888
+ }
889
+
834
890
  /**
835
891
  * The repository root, derived from the workspace root and git's own
836
892
  * `--show-prefix` (the repo→workspace path). Exact and free: the prefix
@@ -1095,6 +1151,8 @@ export interface GitEnumeration {
1095
1151
  trusted: Map<string, string>
1096
1152
  /** Whether the worktree had uncommitted changes; null when `git status` failed. */
1097
1153
  dirty: boolean | null
1154
+ /** `git var -l`'s listing (merged config, then git's variables); null when it failed. */
1155
+ vars: string | null
1098
1156
  /**
1099
1157
  * What `git status` listed, workspace-relative: modified, staged or deleted
1100
1158
  * paths (both sides of a rename) and untracked files. Null when it failed.
@@ -1259,7 +1317,20 @@ export async function startGitEnumeration(
1259
1317
  }
1260
1318
  }
1261
1319
  const indexKeyRead = readIndexKey()
1262
- const [ls, status, vars] = await running
1320
+ const gitPrefix = facts?.prefix ?? ''
1321
+ const gitDir = facts?.commonDir ?? ''
1322
+ const ls = await listing
1323
+ const parsedLs =
1324
+ ls !== null && ls.exitCode === 0
1325
+ ? parseLsFilesOutput(ls.stdout.length === 0 ? [] : ls.stdout.split('\0'), ls.undecodable)
1326
+ : undefined
1327
+ const early =
1328
+ parsedLs !== undefined &&
1329
+ parsedLs.oids.size > 0 &&
1330
+ attributesInIndex({ tracked: parsedLs.files, workspaceRoot, gitDir, gitPrefix, pathspecs })
1331
+ ? spawnGit(CHECK_ATTR, [...parsedLs.oids.keys()].join('\0'))
1332
+ : undefined
1333
+ const [, status, vars] = await running
1263
1334
  // The key stands for the index `ls-files` listed only when that index
1264
1335
  // was written before the enumeration began (git writes it by rename, so
1265
1336
  // the stamp moves with any write) and is still in place after it: a
@@ -1281,20 +1352,14 @@ export async function startGitEnumeration(
1281
1352
  if (ls === null) {
1282
1353
  throw gitSpawnRefusal(workspaceRoot)
1283
1354
  }
1284
- if (ls.exitCode !== 0) {
1355
+ if (parsedLs === undefined) {
1285
1356
  const stderr = ls.stderr.trim()
1286
1357
  throw new UserError(
1287
1358
  `vx requires git: ${workspaceRoot} is not inside a git work tree. ` +
1288
1359
  `Run 'git init' in your workspace root.${stderr ? ` (git: ${stderr})` : ''}`,
1289
1360
  )
1290
1361
  }
1291
- const {
1292
- files: tracked,
1293
- oids,
1294
- flagged,
1295
- gitlinks,
1296
- undecodable,
1297
- } = parseLsFilesOutput(ls.stdout.length === 0 ? [] : ls.stdout.split('\0'), ls.undecodable)
1362
+ const { files: tracked, oids, flagged, gitlinks, undecodable } = parsedLs
1298
1363
  // Normalize `status`'s repo-root-relative paths to workspace-relative (strip
1299
1364
  // the `--show-prefix`) so the dirty set is keyed identically to the trusted
1300
1365
  // OID map. Without this, when the workspace root is a git subdir, a modified
@@ -1302,8 +1367,6 @@ export async function startGitEnumeration(
1302
1367
  // a STALE cache hit serving old outputs. Empty prefix (workspace == git root,
1303
1368
  // the common case) is a zero-cost no-op. Paths above the workspace can't be
1304
1369
  // inputs, so they drop out of the set.
1305
- const gitPrefix = facts?.prefix ?? ''
1306
- const gitDir = facts?.commonDir ?? ''
1307
1370
  const parsedStatus =
1308
1371
  status !== null && status.exitCode === 0
1309
1372
  ? parseStatusOutput(status.stdout, status.undecodable)
@@ -1357,6 +1420,7 @@ export async function startGitEnumeration(
1357
1420
  pathspecs,
1358
1421
  gitVars: vars !== null && vars.exitCode === 0 ? vars.stdout : '',
1359
1422
  spawnGit,
1423
+ early,
1360
1424
  })
1361
1425
  if (vars !== null && vars.exitCode === 0 && fileModeIgnored(vars.stdout)) {
1362
1426
  restampModes(trusted, workspaceRoot)
@@ -1368,6 +1432,7 @@ export async function startGitEnumeration(
1368
1432
  all,
1369
1433
  trusted,
1370
1434
  dirty: worktreeDirty,
1435
+ vars: vars !== null && vars.exitCode === 0 ? vars.stdout : null,
1371
1436
  changed: dirty === null ? null : [...dirty, ...untracked],
1372
1437
  untracked: dirty === null ? null : untracked,
1373
1438
  undecodable,
@@ -1436,6 +1501,7 @@ export async function applyGitEnumeration(
1436
1501
  await dropResizedOids(enumeration, memo)
1437
1502
  const { all, trusted } = enumeration
1438
1503
  cache.setWorktreeDirty(enumeration.dirty)
1504
+ cache.setGitVars(enumeration.vars)
1439
1505
  cache.enumeratedAtMs = enumeration.startedAtMs
1440
1506
  cache.markUndecodable(enumeration.undecodable.map((rel) => path.join(workspaceRoot, rel)))
1441
1507
  // Sort once, then each project's files are a contiguous range found
@@ -8,6 +8,12 @@
8
8
 
9
9
  /** `mtime_ms` of a recorded output prefix that did not exist when the snapshot was taken. */
10
10
  const ABSENT_DIR_MTIME = -1
11
+ /**
12
+ * `mtime_ms` of a recorded output prefix that was a regular file: a literal
13
+ * output naming one (`public/app.js`). The set under it is the file alone
14
+ * while it stays a regular file; its bytes are the per-file check's.
15
+ */
16
+ const FILE_PREFIX_MTIME = -2
11
17
 
12
18
  import type { Database } from 'bun:sqlite'
13
19
  import { inHashes, lazyStatement } from './schema.js'
@@ -244,7 +250,14 @@ export class OutputIndex {
244
250
  }
245
251
  return false
246
252
  }
247
- if (!st.isDirectory()) return false
253
+ if (!st.isDirectory()) {
254
+ // A literal output naming a file walked its glob on every hit: two
255
+ // of this repo's site tasks, ~6 ms of each warm run (U-5).
256
+ if (!isPrefix || !st.isFile()) return false
257
+ rows.push([rel, FILE_PREFIX_MTIME])
258
+ files.push(rel)
259
+ return true
260
+ }
248
261
  rows.push([rel, st.mtimeMs])
249
262
  if (rows.length > OUTPUT_DIRS_CAP) return false
250
263
  let entries
@@ -379,6 +392,7 @@ export class OutputIndex {
379
392
  return r.mtimeMs === ABSENT_DIR_MTIME
380
393
  }
381
394
  if (r.mtimeMs === ABSENT_DIR_MTIME) return false
395
+ if (r.mtimeMs === FILE_PREFIX_MTIME) return st.isFile()
382
396
  return st.isDirectory() && Math.abs(st.mtimeMs - r.mtimeMs) < 1
383
397
  })
384
398
  return results.every(Boolean)
@@ -4,9 +4,11 @@
4
4
  // unset env degrades each field to null and NEVER throws. Telemetry must
5
5
  // not be able to fail a build.
6
6
  //
7
- // Cost: ONE git spawn (`captureGitContext`) on every run, plus two more
8
- // (`captureWorkspaceIdentity`, `captureDefaultBranch`) only when a plugin
9
- // declares the `telemetry` capability — nobody else needs those fields.
7
+ // Cost: HEAD read from `.git` on every run (`captureGitContext`), plus
8
+ // `refs/remotes/origin/HEAD` and the enumeration's `remote.origin.url`
9
+ // only when a plugin declares the `telemetry` capability — nobody else
10
+ // needs those fields. Each falls back to a `git` spawn where the files or
11
+ // the enumeration cannot answer.
10
12
 
11
13
  import fs from 'node:fs'
12
14
  import os from 'node:os'
@@ -115,28 +117,9 @@ function readHeadDirect(
115
117
  workspaceRoot: string,
116
118
  ): { commitSha: string; branch: string | null } | null {
117
119
  try {
118
- let gitDir = path.join(workspaceRoot, '.git')
119
- const st = fs.statSync(gitDir)
120
- if (st.isFile()) {
121
- const m = /^gitdir:\s*(.+)$/m.exec(fs.readFileSync(gitDir, 'utf8'))
122
- if (m === null) return null
123
- gitDir = path.resolve(workspaceRoot, m[1]!.trim())
124
- }
125
- let commonDir = gitDir
126
- const commonFile = path.join(gitDir, 'commondir')
127
- if (fs.existsSync(commonFile)) {
128
- commonDir = path.resolve(gitDir, fs.readFileSync(commonFile, 'utf8').trim())
129
- }
130
- // Reftable storage (`git init --ref-format=reftable`, git 2.45): HEAD
131
- // names `refs/heads/.invalid` and the refs live in `reftable/`, so the
132
- // files say nothing true about HEAD. A leftover `packed-refs` there was
133
- // read as the answer; git is asked instead.
134
- if (
135
- fs.existsSync(path.join(gitDir, 'reftable')) ||
136
- fs.existsSync(path.join(commonDir, 'reftable'))
137
- ) {
138
- return null
139
- }
120
+ const dirs = refDirsOf(workspaceRoot)
121
+ if (dirs === null) return null
122
+ const { gitDir, commonDir } = dirs
140
123
  const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim()
141
124
  const SHA = /^[0-9a-f]{40,64}$/
142
125
  if (SHA.test(head)) return { commitSha: head, branch: null }
@@ -166,6 +149,57 @@ function readHeadDirect(
166
149
  }
167
150
  }
168
151
 
152
+ /**
153
+ * The git dir and common dir behind `workspaceRoot` when its refs are files
154
+ * this code reads: a `.git` directory or a `.git` FILE (`gitdir: …`, a linked
155
+ * worktree, whose shared refs live under `commondir`). Null for a reftable
156
+ * repository (`git init --ref-format=reftable`, git 2.45): HEAD names
157
+ * `refs/heads/.invalid` and the refs live in `reftable/`, so the files say
158
+ * nothing true. A leftover `packed-refs` there was read as the answer.
159
+ * Throws when there is no `.git` at the root (a workspace in a subdirectory).
160
+ */
161
+ function refDirsOf(workspaceRoot: string): { gitDir: string; commonDir: string } | null {
162
+ let gitDir = path.join(workspaceRoot, '.git')
163
+ const st = fs.statSync(gitDir)
164
+ if (st.isFile()) {
165
+ const m = /^gitdir:\s*(.+)$/m.exec(fs.readFileSync(gitDir, 'utf8'))
166
+ if (m === null) return null
167
+ gitDir = path.resolve(workspaceRoot, m[1]!.trim())
168
+ }
169
+ let commonDir = gitDir
170
+ const commonFile = path.join(gitDir, 'commondir')
171
+ if (fs.existsSync(commonFile)) {
172
+ commonDir = path.resolve(gitDir, fs.readFileSync(commonFile, 'utf8').trim())
173
+ }
174
+ if (
175
+ fs.existsSync(path.join(gitDir, 'reftable')) ||
176
+ fs.existsSync(path.join(commonDir, 'reftable'))
177
+ ) {
178
+ return null
179
+ }
180
+ return { gitDir, commonDir }
181
+ }
182
+
183
+ /**
184
+ * `refs/remotes/origin/HEAD`'s target from the files, as `git symbolic-ref
185
+ * --short` names it (`origin/main`): `undefined` when the files cannot say
186
+ * (no `.git` at the root, reftable), null when there is no such symbolic
187
+ * ref. A symbolic ref is never packed, so a missing file is git's answer
188
+ * too.
189
+ */
190
+ function readOriginHeadDirect(workspaceRoot: string): string | null | undefined {
191
+ try {
192
+ const dirs = refDirsOf(workspaceRoot)
193
+ if (dirs === null) return undefined
194
+ const file = path.join(dirs.commonDir, 'refs', 'remotes', 'origin', 'HEAD')
195
+ if (!fs.existsSync(file)) return null
196
+ const ref = /^ref:\s*refs\/remotes\/(.+)$/.exec(fs.readFileSync(file, 'utf8').trim())
197
+ return ref === null ? undefined : ref[1]!
198
+ } catch {
199
+ return undefined
200
+ }
201
+ }
202
+
169
203
  /**
170
204
  * The branch a CI provider says this checkout came from, or null.
171
205
  *
@@ -221,30 +255,34 @@ export function captureDefaultBranch(
221
255
  }
222
256
  }
223
257
 
224
- try {
225
- const proc = Bun.spawnSync({
226
- cmd: [
227
- executablePath('git'),
228
- '-C',
229
- workspaceRoot,
230
- 'symbolic-ref',
231
- '--short',
232
- 'refs/remotes/origin/HEAD',
233
- ],
234
- stdout: 'pipe',
235
- stderr: 'ignore',
236
- })
237
- if (proc.exitCode === 0) {
238
- const ref = new TextDecoder().decode(proc.stdout).trim()
239
- // `refs/remotes/origin/HEAD` → `origin/main`; strip the remote prefix.
240
- const slash = ref.indexOf('/')
241
- const name = slash >= 0 ? ref.slice(slash + 1) : ref
242
- if (name.length > 0) return name
258
+ // The file first, as `captureGitContext` reads HEAD: a spawn here was
259
+ // ~8 ms of every run a telemetry plugin is declared in.
260
+ let ref = readOriginHeadDirect(workspaceRoot)
261
+ if (ref === undefined) {
262
+ ref = null
263
+ try {
264
+ const proc = Bun.spawnSync({
265
+ cmd: [
266
+ executablePath('git'),
267
+ '-C',
268
+ workspaceRoot,
269
+ 'symbolic-ref',
270
+ '--short',
271
+ 'refs/remotes/origin/HEAD',
272
+ ],
273
+ stdout: 'pipe',
274
+ stderr: 'ignore',
275
+ })
276
+ if (proc.exitCode === 0) ref = new TextDecoder().decode(proc.stdout).trim()
277
+ } catch {
278
+ // git unavailable / no remote HEAD ref: null.
243
279
  }
244
- } catch {
245
- // git unavailable / no remote HEAD ref: null.
246
280
  }
247
- return null
281
+ if (ref === null) return null
282
+ // `refs/remotes/origin/HEAD` → `origin/main`; strip the remote prefix.
283
+ const slash = ref.indexOf('/')
284
+ const name = slash >= 0 ? ref.slice(slash + 1) : ref
285
+ return name.length > 0 ? name : null
248
286
  }
249
287
 
250
288
  // Recognized CI env vars in match-priority order. The first whose value
@@ -324,28 +362,38 @@ export function normalizeRemoteUrl(raw: string): string {
324
362
  * checkout keeps a stable identity across runs; `.vx/` is already
325
363
  * gitignored infrastructure);
326
364
  * 3. unwritable `.vx/` → the root path itself (stable per machine).
327
- * One `git` spawn behind try/catch; call sites gate on telemetry being
328
- * active so a plain run never pays it. Never throws.
365
+ * The URL comes from the run's own `git var -l` when it has one, else one
366
+ * `git` spawn behind try/catch; call sites gate on telemetry being active
367
+ * so a plain run never pays it. Never throws.
329
368
  */
330
- export function captureWorkspaceIdentity(workspaceRoot: string): WorkspaceIdentity {
369
+ export function captureWorkspaceIdentity(
370
+ workspaceRoot: string,
371
+ /**
372
+ * `remote.origin.url` as the run's enumeration read it (`git var -l`, the
373
+ * same merged config): null when it has none. Undefined asks git.
374
+ */
375
+ originUrl?: string | null,
376
+ ): WorkspaceIdentity {
331
377
  const base = workspaceRoot.replace(/\/+$/, '').split('/').pop() || 'workspace'
332
378
  try {
333
- const proc = Bun.spawnSync({
334
- // `config --get`, NOT `remote get-url`: get-url applies insteadOf
335
- // rewrites, so two developers mirroring the same repo through
336
- // different proxies would derive different workspace ids. The raw
337
- // configured URL is the identity.
338
- cmd: [executablePath('git'), '-C', workspaceRoot, 'config', '--get', 'remote.origin.url'],
339
- stdout: 'pipe',
340
- stderr: 'ignore',
341
- })
342
- if (proc.exitCode === 0) {
343
- const url = new TextDecoder().decode(proc.stdout).trim()
344
- if (url.length > 0) {
345
- const normalized = normalizeRemoteUrl(url)
346
- const name = normalized.split('/').pop() || base
347
- return { id: xxh3hex(normalized), name }
348
- }
379
+ let url = originUrl ?? ''
380
+ if (originUrl === undefined) {
381
+ const proc = Bun.spawnSync({
382
+ // `config --get`, NOT `remote get-url`: get-url applies insteadOf
383
+ // rewrites, so two developers mirroring the same repo through
384
+ // different proxies would derive different workspace ids. The raw
385
+ // configured URL is the identity.
386
+ cmd: [executablePath('git'), '-C', workspaceRoot, 'config', '--get', 'remote.origin.url'],
387
+ stdout: 'pipe',
388
+ stderr: 'ignore',
389
+ })
390
+ if (proc.exitCode === 0) url = new TextDecoder().decode(proc.stdout)
391
+ }
392
+ url = url.trim()
393
+ if (url.length > 0) {
394
+ const normalized = normalizeRemoteUrl(url)
395
+ const name = normalized.split('/').pop() || base
396
+ return { id: xxh3hex(normalized), name }
349
397
  }
350
398
  } catch {
351
399
  // git unavailable — fall through to the salt.
@@ -720,9 +720,13 @@ async function runOnBus(
720
720
  // but declines still pays — its answer is only knowable by asking.
721
721
  const hasTelemetryPlugin = prepared.plugins.some((p) => p.telemetry !== undefined)
722
722
  if (hasTelemetryPlugin || options.telemetrySinks !== undefined) {
723
- // Workspace identity (telemetry v2): one git spawn, paid only when a
724
- // telemetry consumer can exist — a plain run never reaches here.
725
- const wsIdentity = captureWorkspaceIdentity(workspaceRoot)
723
+ // Workspace identity (telemetry v2), paid only when a telemetry
724
+ // consumer can exist — a plain run never reaches here. The remote URL
725
+ // is the enumeration's `git var -l`: a spawn here cost ~4 ms.
726
+ const wsIdentity = captureWorkspaceIdentity(
727
+ workspaceRoot,
728
+ gitFilesCache.configValue('remote.origin.url'),
729
+ )
726
730
  runContextRecord = {
727
731
  runId,
728
732
  vxVersion: VERSION,
@@ -192,6 +192,17 @@ export interface TaskTelemetry {
192
192
  notReady?: 'timeout' | 'exited' | 'spawn'
193
193
  /** On a task that retried: each attempt that failed before the last, in order. Additive. */
194
194
  failedAttempts?: readonly FailedAttempt[]
195
+ /** On a task proved flaky this run: its key's passes and failures on record. Additive. */
196
+ flaky?: { passes: number; failures: number }
197
+ /** On a sandboxed task: the violation lines themselves, one per denial. Additive. */
198
+ sandboxViolationLines?: readonly string[]
199
+ /** On a cache hit: how long the run that stored the entry took — the time this hit saved. Additive. */
200
+ storedDurationMs?: number
201
+ /** On a cache hit: the CPU and peak memory of the run that stored it. Additive. */
202
+ storedCpuMs?: number
203
+ storedPeakRssBytes?: number
204
+ /** How long an `admit` policy held the task once it was ready. Additive. */
205
+ admissionHeldMs?: number
195
206
  /**
196
207
  * On a cache hit: whether outputs were written this run (`true`) or the
197
208
  * disk already matched the entry and nothing was restored (`false`, an
@@ -718,6 +729,14 @@ export function taskTelemetryOf(o: TaskOutcome): TaskTelemetry {
718
729
  if (o.sandboxViolations !== undefined) t.sandboxViolations = o.sandboxViolations
719
730
  if (o.notReady !== undefined) t.notReady = o.notReady
720
731
  if (o.failedAttempts !== undefined) t.failedAttempts = o.failedAttempts
732
+ if (o.flaky !== undefined) t.flaky = o.flaky
733
+ if (o.sandboxViolationLines !== undefined && o.sandboxViolationLines.length > 0) {
734
+ t.sandboxViolationLines = o.sandboxViolationLines
735
+ }
736
+ if (o.storedDurationMs !== undefined) t.storedDurationMs = o.storedDurationMs
737
+ if (o.storedCpuMs !== undefined) t.storedCpuMs = o.storedCpuMs
738
+ if (o.storedPeakRssBytes !== undefined) t.storedPeakRssBytes = o.storedPeakRssBytes
739
+ if (o.admissionHeldMs !== undefined) t.admissionHeldMs = o.admissionHeldMs
721
740
  if (isCacheHit(o.status)) t.restored = o.restored === true
722
741
  if (o.wallclockStartNs !== undefined) t.wallclockStartNs = o.wallclockStartNs.toString()
723
742
  if (o.wallclockEndNs !== undefined) t.wallclockEndNs = o.wallclockEndNs.toString()
@@ -165,6 +165,10 @@ const IMPURE_LITERAL_RE = /constructor|__proto__|prototype/
165
165
  const IMPORT_RE =
166
166
  /\b(?:import|export)\b(?:(?!\b(?:import|export)\b)[^;])*?\bfrom\s*\0(\d+)\0|\bimport\s*\0(\d+)\0/g
167
167
 
168
+ // `import` as a keyword. Followed by `:` it is a property key (a task named
169
+ // `import`): no import form, static or dynamic, puts a colon after the word.
170
+ const IMPORT_WORD_RE = /\bimport\b(?!\s*:)/g
171
+
168
172
  /**
169
173
  * Every static import in `source` as its specifier and statement, or `null`
170
174
  * when the source cannot be lexed or holds an `import` no form above
@@ -181,9 +185,9 @@ function staticImports(
181
185
  let seen = 0
182
186
  for (const m of code.matchAll(IMPORT_RE)) {
183
187
  imports.push({ spec: strings[Number(m[1] ?? m[2])]!, statement: m[0] })
184
- seen += m[0].match(/\bimport\b/g)?.length ?? 0
188
+ seen += m[0].match(IMPORT_WORD_RE)?.length ?? 0
185
189
  }
186
- if (seen !== (code.match(/\bimport\b/g)?.length ?? 0)) return null
190
+ if (seen !== (code.match(IMPORT_WORD_RE)?.length ?? 0)) return null
187
191
  return { code, strings, imports }
188
192
  }
189
193