From 3199561b4bcd8fc5ccb33d5fdc8426c281bc4c25 Mon Sep 17 00:00:00 2001 From: kilisamemarisaaa <1798456934@qq.com> Date: Sat, 5 Sep 2026 01:37:39 +0800 Subject: [PATCH] fix(vsh): hash wildcard publint outputs --- .../vsh/src/publint/__tests__/cache.test.ts | 33 ++++++++++++++++ scripts/vsh/src/publint/index.ts | 38 ++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/scripts/vsh/src/publint/__tests__/cache.test.ts b/scripts/vsh/src/publint/__tests__/cache.test.ts index b1ff30a87..bd982a296 100644 --- a/scripts/vsh/src/publint/__tests__/cache.test.ts +++ b/scripts/vsh/src/publint/__tests__/cache.test.ts @@ -58,4 +58,37 @@ describe('publint cache key', () => { expect(updatedHash).not.toBe(initialHash); }); + + it('invalidates when a wildcard output file is created', async () => { + const packageDir = join(process.cwd(), '.tmp-publint-cache-test'); + tempDirectories.push(packageDir); + await mkdir(join(packageDir, 'dist'), { recursive: true }); + + const packageJson = { + exports: { + '.': './dist/*.mjs', + }, + }; + + const missingOutputHash = await getPackageHash(packageJson, packageDir); + await writeFile(join(packageDir, 'dist/index.mjs'), 'export {}'); + const createdOutputHash = await getPackageHash(packageJson, packageDir); + + expect(createdOutputHash).not.toBe(missingOutputHash); + }); + + it('changes when a wildcard output file is modified', async () => { + const packageDir = join(process.cwd(), '.tmp-publint-cache-test'); + tempDirectories.push(packageDir); + await mkdir(join(packageDir, 'dist'), { recursive: true }); + const outputPath = join(packageDir, 'dist/index.mjs'); + await writeFile(outputPath, 'export const value = 1'); + + const packageJson = { exports: { '.': './dist/*.mjs' } }; + const initialHash = await getPackageHash(packageJson, packageDir); + await writeFile(outputPath, 'export const value = 100'); + const updatedHash = await getPackageHash(packageJson, packageDir); + + expect(updatedHash).not.toBe(initialHash); + }); }); diff --git a/scripts/vsh/src/publint/index.ts b/scripts/vsh/src/publint/index.ts index 62282d27f..6ba0581d8 100644 --- a/scripts/vsh/src/publint/index.ts +++ b/scripts/vsh/src/publint/index.ts @@ -1,7 +1,7 @@ import type { CAC } from 'cac'; import type { Result } from 'publint'; -import { readFile, stat } from 'node:fs/promises'; +import { glob, readFile, stat } from 'node:fs/promises'; import { basename, dirname, join, resolve } from 'node:path'; import { @@ -67,6 +67,34 @@ function collectRelativePaths(value: unknown, paths = new Set()) { return paths; } +/** + * Expand wildcard package targets before collecting their filesystem state. + * Keep an unmatched pattern so creating its first match also invalidates the + * cache. + */ +async function expandRelativePath(relativePath: string, pkgDir: string) { + if (!relativePath.includes('*')) { + return [relativePath]; + } + + const matches: string[] = []; + try { + for await (const match of glob(relativePath, { cwd: pkgDir })) { + matches.push(match); + } + } catch { + return [relativePath]; + } + + if (matches.length === 0) { + return [relativePath]; + } + + return matches.map((match) => + match.startsWith('./') ? match : `./${match}`, + ); +} + /** * Include referenced package files in the cache key. Publint checks the * filesystem as well as package.json, so a build can change its result without @@ -76,7 +104,13 @@ async function getPackageHash( pkgJson: Record, pkgDir: string, ) { - const relativePaths = [...collectRelativePaths(pkgJson)].toSorted(); + const declaredPaths = [...collectRelativePaths(pkgJson)]; + const expandedPaths = await Promise.all( + declaredPaths.map((relativePath) => + expandRelativePath(relativePath, pkgDir), + ), + ); + const relativePaths = expandedPaths.flat().toSorted(); const fileStates = await Promise.all( relativePaths.map(async (relativePath) => { const targetPath = resolve(pkgDir, relativePath);