@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 +1 -1
- package/package.json +2 -2
- package/src/index.ts +7 -42
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|