From c320da79edd173c06b0be08228e891bcd0457dca Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 16 May 2026 09:08:14 +0800 Subject: [PATCH] fix(test): avoid scanning extension ids --- scripts/lib/changed-extensions.mjs | 31 +++++++++++++++---- test/scripts/test-extension.test.ts | 47 +++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/scripts/lib/changed-extensions.mjs b/scripts/lib/changed-extensions.mjs index 2e6763dd8c4..195ff4fc5dc 100644 --- a/scripts/lib/changed-extensions.mjs +++ b/scripts/lib/changed-extensions.mjs @@ -70,11 +70,19 @@ function listChangedPaths(base, head = "HEAD") { .filter((line) => line.length > 0); } -function hasExtensionPackage(extensionId) { - return fs.existsSync(path.join(repoRoot, BUNDLED_PLUGIN_ROOT_DIR, extensionId, "package.json")); +function listAvailableExtensionIdsFromGit() { + const packageFiles = runGit(["ls-files", "--", `:(glob)${BUNDLED_PLUGIN_PATH_PREFIX}*/package.json`]) + .split("\n") + .map((line) => normalizeRelative(line.trim())) + .filter((line) => line.length > 0); + return packageFiles + .map((file) => file.match(new RegExp(`^${BUNDLED_PLUGIN_PATH_PREFIX}([^/]+)/package\\.json$`))) + .filter((match) => match) + .map((match) => match[1]) + .toSorted((left, right) => left.localeCompare(right)); } -export function listAvailableExtensionIds() { +function listAvailableExtensionIdsFromDirectory() { const extensionsDir = path.join(repoRoot, BUNDLED_PLUGIN_ROOT_DIR); if (!fs.existsSync(extensionsDir)) { return []; @@ -84,11 +92,22 @@ export function listAvailableExtensionIds() { .readdirSync(extensionsDir, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) - .filter((extensionId) => hasExtensionPackage(extensionId)) + .filter((extensionId) => + fs.existsSync(path.join(repoRoot, BUNDLED_PLUGIN_ROOT_DIR, extensionId, "package.json")), + ) .toSorted((left, right) => left.localeCompare(right)); } +export function listAvailableExtensionIds() { + try { + return listAvailableExtensionIdsFromGit(); + } catch { + return listAvailableExtensionIdsFromDirectory(); + } +} + export function detectChangedExtensionIds(changedPaths) { + const availableExtensionIds = new Set(listAvailableExtensionIds()); const extensionIds = new Set(); for (const rawPath of changedPaths) { @@ -102,14 +121,14 @@ export function detectChangedExtensionIds(changedPaths) { ); if (extensionMatch) { const extensionId = extensionMatch[1]; - if (hasExtensionPackage(extensionId)) { + if (availableExtensionIds.has(extensionId)) { extensionIds.add(extensionId); } continue; } const pairedCoreMatch = relativePath.match(/^src\/([^/]+)(?:\/|$)/); - if (pairedCoreMatch && hasExtensionPackage(pairedCoreMatch[1])) { + if (pairedCoreMatch && availableExtensionIds.has(pairedCoreMatch[1])) { extensionIds.add(pairedCoreMatch[1]); } } diff --git a/test/scripts/test-extension.test.ts b/test/scripts/test-extension.test.ts index 3926012b549..36fd80d75d9 100644 --- a/test/scripts/test-extension.test.ts +++ b/test/scripts/test-extension.test.ts @@ -252,6 +252,53 @@ describe("scripts/test-extension.mjs", () => { ); }); + it("lists available extension ids from git without reading extension directories", () => { + const output = execFileSync( + process.execPath, + [ + "--input-type=module", + "--eval", + ` + import fs from "node:fs"; + import { syncBuiltinESMExports } from "node:module"; + const counts = { existsSync: 0, readdirSync: 0 }; + const originalExistsSync = fs.existsSync; + const originalReaddirSync = fs.readdirSync; + fs.existsSync = (...args) => { + counts.existsSync += 1; + return originalExistsSync(...args); + }; + fs.readdirSync = (...args) => { + counts.readdirSync += 1; + return originalReaddirSync(...args); + }; + syncBuiltinESMExports(); + const { detectChangedExtensionIds, listAvailableExtensionIds } = await import("./scripts/lib/changed-extensions.mjs"); + const ids = listAvailableExtensionIds(); + const changed = detectChangedExtensionIds([ + "extensions/slack/src/channel.ts", + "src/line/message.test.ts", + "extensions/not-real/package.json", + ]); + console.log(JSON.stringify({ changed, counts, ids: ids.length })); + `, + ], + { + cwd: process.cwd(), + encoding: "utf8", + }, + ); + + const payload = JSON.parse(output) as { + changed: string[]; + counts: { existsSync: number; readdirSync: number }; + ids: number; + }; + expect(payload.changed).toEqual(["line", "slack"]); + expect(payload.ids).toBeGreaterThan(0); + expect(payload.counts).toEqual({ existsSync: 0, readdirSync: 0 }); + }); + it("can fail safe to all extensions when the base revision is unavailable", () => { const extensionIds = listChangedExtensionIds({ base: "refs/heads/openclaw-test-missing-base",