@vzn/vx-lockfile 0.0.649 → 0.0.650

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/README.md CHANGED
@@ -80,7 +80,7 @@ These edges are what was installed, so they differ from a guess over manifest ra
80
80
 
81
81
  ## Cost
82
82
 
83
- Owning the file, the per-project key, the memo and the `--affected` diff are core's `lockfileClaim`; this package is the parsers, and they are internal: it exports `pnpm`, `bun`, `npm`, `yarn` and `LockfileOptions`. A lockfile is parsed **once per content**: the digests and each importer's edges are memoised under the cache dir (`lockfile-claims/<file>.json`) by the file's xxh3 and the plugin, its parsers' source and what core's digest step answers on a fixed probe graph included (an upgrade that changes how a digest is built never replays an old digest), so a warm run pays one read, one hash and one small JSON read — never a parse — and the read happens once per run, not per task. `--affected` reads which files the lockfile names from the same memo. Under `scope: 'workspace'` the file is parsed for its edges alone, once per content, so an unreadable lockfile is refused there too. The settings files are read once per run. The digest is one hash per strongly connected component of the dependency graph (lockfiles carry cycles), children first, so a 1000-importer / 3000-package lockfile digests in ~20 ms when it does change.
83
+ Owning the file, the per-project key, the memo and the `--affected` diff are core's `lockfileClaim`; this package is the parsers, and they are internal: it exports `pnpm`, `bun`, `npm`, `yarn` and `LockfileOptions`. A lockfile is parsed **once per content**: the digests, each importer's edges and the dependency names it records are memoised in core's cache.db (`lockfile_memos`, one row per file and claimant) by the file's xxh3 and the plugin, its parsers' source and what core's digest step answers on a fixed probe graph included (an upgrade that changes how a digest is built never replays an old digest), so a warm run pays one read, one hash and one row read — never a parse — and the read happens once per run, not per task. `--affected` reads which files the lockfile names from the same memo. Under `scope: 'workspace'` the file is parsed for its edges alone, once per content, so an unreadable lockfile is refused there too. The settings files are read once per run. The digest is one hash per strongly connected component of the dependency graph (lockfiles carry cycles), children first, so a 1000-importer / 3000-package lockfile digests in ~20 ms when it does change.
84
84
 
85
85
  ## `--affected`
86
86
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx-lockfile",
3
- "version": "0.0.649",
3
+ "version": "0.0.650",
4
4
  "license": "MIT",
5
5
  "description": "Lockfile plugins for vx — pnpm(), bun(), npm(), yarn(): each keys every task on its project's own resolved dependency closure from the package manager's lockfile, so a lockfile change re-keys only the projects it reaches; --affected follows the same answer.",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "peerDependencies": {
38
- "@vzn/vx": "^0.0.649"
38
+ "@vzn/vx": "^0.0.650"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
package/src/index.ts CHANGED
@@ -18,7 +18,6 @@
18
18
  //
19
19
  // Imports core only through the public `@vzn/vx` specifier.
20
20
  import { readdirSync, readFileSync } from 'node:fs'
21
- import { mkdir, rename, rm } from 'node:fs/promises'
22
21
  import { createRequire } from 'node:module'
23
22
  import path from 'node:path'
24
23
  import {
@@ -326,6 +325,10 @@ function plugin(manager: Manager, options: LockfileOptions): VxPlugin {
326
325
  version: digestVersion(),
327
326
  scope,
328
327
  importers: guarded((text: string) => manager.importers(text)),
328
+ recorded: guarded(
329
+ (text: string) =>
330
+ new Map([...manager.recorded(text)].map(([i, deps]) => [i, [...deps.keys()]])),
331
+ ),
329
332
  // Read before `digest` on the key path: a malformed bun.lock refused
330
333
  // there as a bare "JSONC Parse error", no file named (fuzzed, L-16).
331
334
  ...(extraFiles !== undefined
@@ -476,7 +479,9 @@ function plugin(manager: Manager, options: LockfileOptions): VxPlugin {
476
479
  const missing = await staleness(
477
480
  ctx.workspaceRoot,
478
481
  importers,
479
- await recordedOnce(ctx.cacheDir, file, text, manager.recorded),
482
+ (await claimsAt(ctx.workspaceRoot)
483
+ .find((c) => c.file === file)!
484
+ .claim.recorded(ctx)) ?? new Map(),
480
485
  await dirs,
481
486
  manifest,
482
487
  manager.member,
@@ -539,46 +544,6 @@ function plugin(manager: Manager, options: LockfileOptions): VxPlugin {
539
544
  })
540
545
  }
541
546
 
542
- /**
543
- * The dependency names each importer records, kept per lockfile content:
544
- * parsing them (pnpm's importers block, ~2 ms on solid, and the parser's
545
- * load) was on every run, and only the lockfile text decides them.
546
- */
547
- async function recordedOnce(
548
- cacheDir: string,
549
- file: string,
550
- text: string,
551
- recorded: (text: string) => ReadonlyMap<string, ReadonlyMap<string, unknown>>,
552
- ): Promise<ReadonlyMap<string, ReadonlySet<string>>> {
553
- const hash = `${RECORDED_VERSION}:${Bun.hash.xxHash3(text).toString(16)}`
554
- const memo = path.join(cacheDir, 'lockfile-claims', `${file}.recorded.json`)
555
- try {
556
- const kept = (await Bun.file(memo).json()) as { hash: string; names: Record<string, string[]> }
557
- if (kept.hash === hash) {
558
- return new Map(Object.entries(kept.names).map(([i, names]) => [i, new Set(names)]))
559
- }
560
- } catch {
561
- // None kept, or unreadable: parsed below.
562
- }
563
- const out = new Map([...recorded(text)].map(([i, deps]) => [i, new Set(deps.keys())]))
564
- const names = Object.fromEntries([...out].map(([i, set]) => [i, [...set]]))
565
- // Write-then-rename, so a concurrent run reads a whole memo or none. The
566
- // memo is a speed-up: a failed write costs the next run a parse.
567
- const tmp = `${memo}.tmp-${process.pid}-${Date.now()}`
568
- try {
569
- await mkdir(path.dirname(memo), { recursive: true })
570
- await Bun.write(tmp, JSON.stringify({ hash, names }))
571
- await rename(tmp, memo)
572
- } catch {
573
- // Left behind, one more temp file piled up in the cache every run.
574
- await rm(tmp, { force: true }).catch(() => {})
575
- }
576
- return out
577
- }
578
-
579
- /** Bumped when what `recorded` returns for the same text changes. */
580
- const RECORDED_VERSION = 1
581
-
582
547
  /** Up to five names, then a count of the rest. */
583
548
  function listed(names: readonly string[]): string {
584
549
  return names.length <= 5