From 78e3e481a525588572fb584d36f39b807664f013 Mon Sep 17 00:00:00 2001 From: Frederik Bosch <6979916+fbosch@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:52:31 +0100 Subject: [PATCH 1/6] perf(verify): optimize file existence checks --- .gitignore | 1 + src/verify.ts | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index da8394e..c2da450 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ docs.lock coverage TODO.md .docs/ +benchmarks/ diff --git a/src/verify.ts b/src/verify.ts index 218e748..aab014d 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -46,13 +46,18 @@ export const verifyCache = async (options: VerifyOptions) => { let sizeMismatchCount = 0; for await (const entry of streamManifestEntries(directory)) { const filePath = path.join(directory, entry.path); - if (!(await exists(filePath))) { - missingCount += 1; - continue; - } - const info = await stat(filePath); - if (info.size !== entry.size) { - sizeMismatchCount += 1; + try { + const info = await stat(filePath); + if (info.size !== entry.size) { + sizeMismatchCount += 1; + } + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT" || code === "ENOTDIR") { + missingCount += 1; + continue; + } + throw error; } } const issues: string[] = []; From a048045ed8bde7cf3009ab917f005a06da8ce97a Mon Sep 17 00:00:00 2001 From: Frederik Bosch <6979916+fbosch@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:57:31 +0100 Subject: [PATCH 2/6] perf: normalization --- src/materialize.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/materialize.ts b/src/materialize.ts index 533aadc..b934a30 100644 --- a/src/materialize.ts +++ b/src/materialize.ts @@ -133,11 +133,14 @@ export const materializeSource = async (params: MaterializeParams) => { onlyFiles: true, followSymbolicLinks: false, }); - files.sort((left, right) => - normalizePath(left).localeCompare(normalizePath(right)), - ); + const entries = files + .map((relativePath) => ({ + relativePath, + normalized: normalizePath(relativePath), + })) + .sort((left, right) => left.normalized.localeCompare(right.normalized)); const targetDirs = new Set(); - for (const relativePath of files) { + for (const { relativePath } of entries) { targetDirs.add(path.dirname(relativePath)); } await Promise.all( @@ -149,7 +152,10 @@ export const materializeSource = async (params: MaterializeParams) => { let fileCount = 0; const concurrency = Math.max( 1, - Math.min(files.length, Math.max(8, Math.min(128, os.cpus().length * 8))), + Math.min( + entries.length, + Math.max(8, Math.min(128, os.cpus().length * 8)), + ), ); const manifestPath = path.join(tempDir, MANIFEST_FILENAME); const manifestStream = createWriteStream(manifestPath, { @@ -177,12 +183,11 @@ export const materializeSource = async (params: MaterializeParams) => { }); }; - for (let i = 0; i < files.length; i += concurrency) { - const batch = files.slice(i, i + concurrency); + for (let i = 0; i < entries.length; i += concurrency) { + const batch = entries.slice(i, i + concurrency); const results = await Promise.all( - batch.map(async (relativePath) => { - const relNormalized = normalizePath(relativePath); - const filePath = path.join(params.repoDir, relativePath); + batch.map(async (entry) => { + const filePath = path.join(params.repoDir, entry.relativePath); const fileHandle = await openFileNoFollow(filePath); if (!fileHandle) { return null; @@ -192,7 +197,7 @@ export const materializeSource = async (params: MaterializeParams) => { if (!stats.isFile()) { return null; } - const targetPath = path.join(tempDir, relativePath); + const targetPath = path.join(tempDir, entry.relativePath); ensureSafePath(tempDir, targetPath); if (stats.size >= STREAM_COPY_THRESHOLD_BYTES) { const reader = createReadStream(filePath, { @@ -206,7 +211,7 @@ export const materializeSource = async (params: MaterializeParams) => { await writeFile(targetPath, data); } return { - path: relNormalized, + path: entry.normalized, size: stats.size, }; } finally { From 1305dd7812ba5ca6bd0178650cb7d6ceed781a3f Mon Sep 17 00:00:00 2001 From: Frederik Bosch <6979916+fbosch@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:00:31 +0100 Subject: [PATCH 3/6] perf(sync): cache docs presence checks --- src/sync.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 8b4097a..228adc3 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -275,6 +275,7 @@ export const runSync = async (options: SyncOptions, deps: SyncDeps = {}) => { const defaults = plan.defaults; const runFetch = deps.fetchSource ?? fetchSource; const runMaterialize = deps.materializeSource ?? materializeSource; + const docsPresence = new Map(); const buildJobs = async (ids?: string[], force?: boolean) => { const pick = ids?.length ? plan.results.filter((result) => ids.includes(result.id)) @@ -285,9 +286,16 @@ export const runSync = async (options: SyncOptions, deps: SyncDeps = {}) => { if (!source) { return null; } - const docsPresent = await hasDocs(plan.cacheDir, result.id); + if (force) { + return { result, source }; + } + let docsPresent = docsPresence.get(result.id); + if (docsPresent === undefined) { + docsPresent = await hasDocs(plan.cacheDir, result.id); + docsPresence.set(result.id, docsPresent); + } const needsMaterialize = - force || result.status !== "up-to-date" || !docsPresent; + result.status !== "up-to-date" || !docsPresent; return needsMaterialize ? { result, source } : null; }), ); From 3fed33c2eadda130311dde7e6c14bfb007c3bdf6 Mon Sep 17 00:00:00 2001 From: Frederik Bosch <6979916+fbosch@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:04:55 +0100 Subject: [PATCH 4/6] ci: add Node.js 18,20,22 matrix --- .github/workflows/audit.yml | 2 +- .github/workflows/ci.yml | 3 ++- .github/workflows/pkg-pr-new-preview.yml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index fe450fe..264fcec 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aebc814..a518980 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] + node-version: [18, 20, 22] steps: - name: Checkout uses: actions/checkout@v4 @@ -26,7 +27,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: ${{ matrix.node-version }} cache: pnpm - name: Install dependencies diff --git a/.github/workflows/pkg-pr-new-preview.yml b/.github/workflows/pkg-pr-new-preview.yml index 7c96421..22b8d03 100644 --- a/.github/workflows/pkg-pr-new-preview.yml +++ b/.github/workflows/pkg-pr-new-preview.yml @@ -31,7 +31,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies From 2e302bdc03f2fb82947e85ed61b53ae5af119869 Mon Sep 17 00:00:00 2001 From: Frederik Bosch <6979916+fbosch@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:13:13 +0100 Subject: [PATCH 5/6] ci: run full OS matrix only on PRs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a518980..85a2866 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: ${{ fromJSON(github.event_name == 'pull_request' && '["ubuntu-latest","macos-latest","windows-latest"]' || '["ubuntu-latest"]') }} node-version: [18, 20, 22] steps: - name: Checkout From b388bfe87962b0bc73abbe0e33f9a7ee28251212 Mon Sep 17 00:00:00 2001 From: Frederik Bosch <6979916+fbosch@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:14:18 +0100 Subject: [PATCH 6/6] fix(cache): improve verifyCache error handling --- src/verify.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/verify.ts b/src/verify.ts index aab014d..4311074 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -79,13 +79,17 @@ export const verifyCache = async (options: VerifyOptions) => { ok: issues.length === 0, issues, }; - } catch (_error) { - return { - ok: false, - issues: [ - label === "source" ? "missing manifest" : "missing target manifest", - ], - }; + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT" || code === "ENOTDIR") { + return { + ok: false, + issues: [ + label === "source" ? "missing manifest" : "missing target manifest", + ], + }; + } + throw error; } };