diff --git a/docs/help/testing.md b/docs/help/testing.md index e9829a67653..f7dda0facb2 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -61,6 +61,7 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost): - `pnpm test`, `pnpm test:watch`, and `pnpm test:perf:imports` route explicit file/directory targets through scoped lanes first, so `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` avoids paying the full root project startup tax. - `pnpm test:changed` expands changed git paths into the same scoped lanes when the diff only touches routable source/test files; config/setup edits still fall back to the broad root-project rerun. - Selected `plugin-sdk` and `commands` tests also route through dedicated light lanes that skip `test/setup-openclaw-runtime.ts`; stateful/runtime-heavy files stay on the existing lanes. + - Selected `plugin-sdk` and `commands` helper source files also map changed-mode runs to explicit sibling tests in those light lanes, so helper edits avoid rerunning the full heavy suite for that directory. - Embedded runner note: - When you change message-tool discovery inputs or compaction runtime context, keep both levels of coverage. diff --git a/docs/reference/test.md b/docs/reference/test.md index b5526c3a86b..7633a04cf1e 100644 --- a/docs/reference/test.md +++ b/docs/reference/test.md @@ -15,6 +15,7 @@ title: "Tests" - `pnpm test:changed`: expands changed git paths into scoped Vitest lanes when the diff only touches routable source/test files. Config/setup changes still fall back to the native root projects run so wiring edits rerun broadly when needed. - `pnpm test`: routes explicit file/directory targets through scoped Vitest lanes, but still falls back to the native root projects run when you do a full untargeted sweep. - Selected `plugin-sdk` and `commands` test files now route through dedicated light lanes that keep only `test/setup.ts`, leaving runtime-heavy cases on their existing lanes. +- Selected `plugin-sdk` and `commands` helper source files also map `pnpm test:changed` to explicit sibling tests in those light lanes, so small helper edits avoid rerunning the heavy runtime-backed suites. - Base Vitest config now defaults to `pool: "threads"` and `isolate: false`, with the shared non-isolated runner enabled across the repo configs. - `pnpm test:channels` runs `vitest.channels.config.ts`. - `pnpm test:extensions` runs `vitest.extensions.config.ts`. diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index 4340b0d4504..45d7600f94c 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -3,7 +3,10 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { isChannelSurfaceTestFile } from "../vitest.channel-paths.mjs"; -import { isCommandsLightTestFile } from "../vitest.commands-light-paths.mjs"; +import { + isCommandsLightTarget, + resolveCommandsLightIncludePattern, +} from "../vitest.commands-light-paths.mjs"; import { isAcpxExtensionRoot } from "../vitest.extension-acpx-paths.mjs"; import { isBlueBubblesExtensionRoot } from "../vitest.extension-bluebubbles-paths.mjs"; import { isDiffsExtensionRoot } from "../vitest.extension-diffs-paths.mjs"; @@ -19,7 +22,10 @@ import { isTelegramExtensionRoot } from "../vitest.extension-telegram-paths.mjs" import { isVoiceCallExtensionRoot } from "../vitest.extension-voice-call-paths.mjs"; import { isWhatsAppExtensionRoot } from "../vitest.extension-whatsapp-paths.mjs"; import { isZaloExtensionRoot } from "../vitest.extension-zalo-paths.mjs"; -import { isPluginSdkLightTestFile } from "../vitest.plugin-sdk-paths.mjs"; +import { + isPluginSdkLightTarget, + resolvePluginSdkLightIncludePattern, +} from "../vitest.plugin-sdk-paths.mjs"; import { isBoundaryTestFile, isBundledPluginDependentUnitTestFile } from "../vitest.unit-paths.mjs"; import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./run-vitest.mjs"; @@ -320,7 +326,7 @@ function classifyTarget(arg, cwd) { return "logging"; } if (relative.startsWith("src/plugin-sdk/")) { - return isPluginSdkLightTestFile(relative) ? "pluginSdkLight" : "pluginSdk"; + return isPluginSdkLightTarget(relative) ? "pluginSdkLight" : "pluginSdk"; } if (relative.startsWith("src/process/")) { return "process"; @@ -344,7 +350,7 @@ function classifyTarget(arg, cwd) { return "cli"; } if (relative.startsWith("src/commands/")) { - return isCommandsLightTestFile(relative) ? "commandLight" : "command"; + return isCommandsLightTarget(relative) ? "commandLight" : "command"; } if (relative.startsWith("src/auto-reply/")) { return "autoReply"; @@ -367,6 +373,19 @@ function classifyTarget(arg, cwd) { return "default"; } +function resolveLightLaneIncludePatterns(kind, targetArg, cwd) { + const relative = toRepoRelativeTarget(targetArg, cwd); + if (kind === "pluginSdkLight") { + const includePattern = resolvePluginSdkLightIncludePattern(relative); + return includePattern ? [includePattern] : null; + } + if (kind === "commandLight") { + const includePattern = resolveCommandsLightIncludePattern(relative); + return includePattern ? [includePattern] : null; + } + return null; +} + function createVitestArgs(params) { return [ "exec", @@ -618,7 +637,10 @@ export function buildVitestRunPlans( grouped.every((targetArg) => isFileLikeTarget(toRepoRelativeTarget(targetArg, cwd)))); const includePatterns = useCliTargetArgs ? null - : grouped.map((targetArg) => toScopedIncludePattern(targetArg, cwd)); + : grouped.flatMap((targetArg) => { + const lightLanePatterns = resolveLightLaneIncludePatterns(kind, targetArg, cwd); + return lightLanePatterns ?? [toScopedIncludePattern(targetArg, cwd)]; + }); const scopedTargetArgs = useCliTargetArgs ? grouped : []; plans.push({ config, diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index 7ebab7b6ffa..c5e7d83a119 100644 --- a/src/secrets/target-registry-data.ts +++ b/src/secrets/target-registry-data.ts @@ -467,9 +467,15 @@ const CORE_SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [ }, ]; -const SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [ - ...CORE_SECRET_TARGET_REGISTRY, - ...listChannelSecretTargetRegistryEntries(), -]; +let cachedSecretTargetRegistry: SecretTargetRegistryEntry[] | null = null; -export { SECRET_TARGET_REGISTRY }; +export function getSecretTargetRegistry(): SecretTargetRegistryEntry[] { + if (cachedSecretTargetRegistry) { + return cachedSecretTargetRegistry; + } + cachedSecretTargetRegistry = [ + ...CORE_SECRET_TARGET_REGISTRY, + ...listChannelSecretTargetRegistryEntries(), + ]; + return cachedSecretTargetRegistry; +} diff --git a/src/secrets/target-registry-query.ts b/src/secrets/target-registry-query.ts index 230b68f0180..a8065302c50 100644 --- a/src/secrets/target-registry-query.ts +++ b/src/secrets/target-registry-query.ts @@ -1,6 +1,6 @@ import type { OpenClawConfig } from "../config/config.js"; import { getPath } from "./path-utils.js"; -import { SECRET_TARGET_REGISTRY } from "./target-registry-data.js"; +import { getSecretTargetRegistry } from "./target-registry-data.js"; import { compileTargetRegistryEntry, expandPathTokens, @@ -14,15 +14,19 @@ import type { SecretTargetRegistryEntry, } from "./target-registry-types.js"; -const COMPILED_SECRET_TARGET_REGISTRY = SECRET_TARGET_REGISTRY.map(compileTargetRegistryEntry); -const OPENCLAW_COMPILED_SECRET_TARGETS = COMPILED_SECRET_TARGET_REGISTRY.filter( - (entry) => entry.configFile === "openclaw.json", -); -const AUTH_PROFILES_COMPILED_SECRET_TARGETS = COMPILED_SECRET_TARGET_REGISTRY.filter( - (entry) => entry.configFile === "auth-profiles.json", -); +let compiledSecretTargetRegistryState: { + authProfilesCompiledSecretTargets: CompiledTargetRegistryEntry[]; + authProfilesTargetsById: Map; + compiledSecretTargetRegistry: CompiledTargetRegistryEntry[]; + knownTargetIds: Set; + openClawCompiledSecretTargets: CompiledTargetRegistryEntry[]; + openClawTargetsById: Map; + targetsByType: Map; +} | null = null; -function buildTargetTypeIndex(): Map { +function buildTargetTypeIndex( + compiledSecretTargetRegistry: CompiledTargetRegistryEntry[], +): Map { const byType = new Map(); const append = (type: string, entry: CompiledTargetRegistryEntry) => { const existing = byType.get(type); @@ -32,7 +36,7 @@ function buildTargetTypeIndex(): Map { } byType.set(type, [entry]); }; - for (const entry of COMPILED_SECRET_TARGET_REGISTRY) { + for (const entry of compiledSecretTargetRegistry) { append(entry.targetType, entry); for (const alias of entry.targetTypeAliases ?? []) { append(alias, entry); @@ -41,12 +45,11 @@ function buildTargetTypeIndex(): Map { return byType; } -const TARGETS_BY_TYPE = buildTargetTypeIndex(); -const KNOWN_TARGET_IDS = new Set(COMPILED_SECRET_TARGET_REGISTRY.map((entry) => entry.id)); - -function buildConfigTargetIdIndex(): Map { +function buildConfigTargetIdIndex( + entries: CompiledTargetRegistryEntry[], +): Map { const byId = new Map(); - for (const entry of OPENCLAW_COMPILED_SECRET_TARGETS) { + for (const entry of entries) { const existing = byId.get(entry.id); if (existing) { existing.push(entry); @@ -57,23 +60,29 @@ function buildConfigTargetIdIndex(): Map return byId; } -const OPENCLAW_TARGETS_BY_ID = buildConfigTargetIdIndex(); - -function buildAuthProfileTargetIdIndex(): Map { - const byId = new Map(); - for (const entry of AUTH_PROFILES_COMPILED_SECRET_TARGETS) { - const existing = byId.get(entry.id); - if (existing) { - existing.push(entry); - continue; - } - byId.set(entry.id, [entry]); +function getCompiledSecretTargetRegistryState() { + if (compiledSecretTargetRegistryState) { + return compiledSecretTargetRegistryState; } - return byId; + const compiledSecretTargetRegistry = getSecretTargetRegistry().map(compileTargetRegistryEntry); + const openClawCompiledSecretTargets = compiledSecretTargetRegistry.filter( + (entry) => entry.configFile === "openclaw.json", + ); + const authProfilesCompiledSecretTargets = compiledSecretTargetRegistry.filter( + (entry) => entry.configFile === "auth-profiles.json", + ); + compiledSecretTargetRegistryState = { + authProfilesCompiledSecretTargets, + authProfilesTargetsById: buildConfigTargetIdIndex(authProfilesCompiledSecretTargets), + compiledSecretTargetRegistry, + knownTargetIds: new Set(compiledSecretTargetRegistry.map((entry) => entry.id)), + openClawCompiledSecretTargets, + openClawTargetsById: buildConfigTargetIdIndex(openClawCompiledSecretTargets), + targetsByType: buildTargetTypeIndex(compiledSecretTargetRegistry), + }; + return compiledSecretTargetRegistryState; } -const AUTH_PROFILES_TARGETS_BY_ID = buildAuthProfileTargetIdIndex(); - function normalizeAllowedTargetIds(targetIds?: Iterable): Set | null { if (targetIds === undefined) { return null; @@ -170,7 +179,7 @@ function toResolvedPlanTarget( } export function listSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] { - return COMPILED_SECRET_TARGET_REGISTRY.map((entry) => ({ + return getCompiledSecretTargetRegistryState().compiledSecretTargetRegistry.map((entry) => ({ id: entry.id, targetType: entry.targetType, ...(entry.targetTypeAliases ? { targetTypeAliases: [...entry.targetTypeAliases] } : {}), @@ -194,11 +203,15 @@ export function listSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] { } export function isKnownSecretTargetType(value: unknown): value is string { - return typeof value === "string" && TARGETS_BY_TYPE.has(value); + return ( + typeof value === "string" && getCompiledSecretTargetRegistryState().targetsByType.has(value) + ); } export function isKnownSecretTargetId(value: unknown): value is string { - return typeof value === "string" && KNOWN_TARGET_IDS.has(value); + return ( + typeof value === "string" && getCompiledSecretTargetRegistryState().knownTargetIds.has(value) + ); } export function resolvePlanTargetAgainstRegistry(candidate: { @@ -207,7 +220,7 @@ export function resolvePlanTargetAgainstRegistry(candidate: { providerId?: string; accountId?: string; }): ResolvedPlanTarget | null { - const entries = TARGETS_BY_TYPE.get(candidate.type); + const entries = getCompiledSecretTargetRegistryState().targetsByType.get(candidate.type); if (!entries || entries.length === 0) { return null; } @@ -240,7 +253,7 @@ export function resolvePlanTargetAgainstRegistry(candidate: { } export function resolveConfigSecretTargetByPath(pathSegments: string[]): ResolvedPlanTarget | null { - for (const entry of OPENCLAW_COMPILED_SECRET_TARGETS) { + for (const entry of getCompiledSecretTargetRegistryState().openClawCompiledSecretTargets) { if (!entry.includeInPlan) { continue; } @@ -268,10 +281,11 @@ export function discoverConfigSecretTargetsByIds( targetIds?: Iterable, ): DiscoveredConfigSecretTarget[] { const allowedTargetIds = normalizeAllowedTargetIds(targetIds); + const registryState = getCompiledSecretTargetRegistryState(); const discoveryEntries = resolveDiscoveryEntries({ allowedTargetIds, - defaultEntries: OPENCLAW_COMPILED_SECRET_TARGETS, - entriesById: OPENCLAW_TARGETS_BY_ID, + defaultEntries: registryState.openClawCompiledSecretTargets, + entriesById: registryState.openClawTargetsById, }); return discoverSecretTargetsFromEntries(config, discoveryEntries); } @@ -285,16 +299,17 @@ export function discoverAuthProfileSecretTargetsByIds( targetIds?: Iterable, ): DiscoveredConfigSecretTarget[] { const allowedTargetIds = normalizeAllowedTargetIds(targetIds); + const registryState = getCompiledSecretTargetRegistryState(); const discoveryEntries = resolveDiscoveryEntries({ allowedTargetIds, - defaultEntries: AUTH_PROFILES_COMPILED_SECRET_TARGETS, - entriesById: AUTH_PROFILES_TARGETS_BY_ID, + defaultEntries: registryState.authProfilesCompiledSecretTargets, + entriesById: registryState.authProfilesTargetsById, }); return discoverSecretTargetsFromEntries(store, discoveryEntries); } export function listAuthProfileSecretTargetEntries(): SecretTargetRegistryEntry[] { - return COMPILED_SECRET_TARGET_REGISTRY.filter( + return getCompiledSecretTargetRegistryState().compiledSecretTargetRegistry.filter( (entry) => entry.configFile === "auth-profiles.json" && entry.includeInAudit, ); } diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index f4d99b944b5..c0fe5489a2c 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -93,4 +93,64 @@ describe("scripts/test-projects changed-target routing", () => { }, ]); }); + + it("routes changed plugin-sdk source allowlist files to sibling light tests", () => { + const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [ + "src/plugin-sdk/lazy-value.ts", + ]); + + expect(plans).toEqual([ + { + config: "vitest.plugin-sdk-light.config.ts", + forwardedArgs: [], + includePatterns: ["src/plugin-sdk/lazy-value.test.ts"], + watchMode: false, + }, + ]); + }); + + it("routes changed commands source allowlist files to sibling light tests", () => { + const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [ + "src/commands/status-overview-values.ts", + ]); + + expect(plans).toEqual([ + { + config: "vitest.commands-light.config.ts", + forwardedArgs: [], + includePatterns: ["src/commands/status-overview-values.test.ts"], + watchMode: false, + }, + ]); + }); + + it("keeps non-allowlisted plugin-sdk source files on the heavy lane", () => { + const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [ + "src/plugin-sdk/facade-runtime.ts", + ]); + + expect(plans).toEqual([ + { + config: "vitest.plugin-sdk.config.ts", + forwardedArgs: [], + includePatterns: ["src/plugin-sdk/**/*.test.ts"], + watchMode: false, + }, + ]); + }); + + it("keeps non-allowlisted commands source files on the heavy lane", () => { + const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [ + "src/commands/channels.add.ts", + ]); + + expect(plans).toEqual([ + { + config: "vitest.commands.config.ts", + forwardedArgs: [], + includePatterns: ["src/commands/**/*.test.ts"], + watchMode: false, + }, + ]); + }); }); diff --git a/test/vitest-light-paths.test.ts b/test/vitest-light-paths.test.ts new file mode 100644 index 00000000000..ed01eb64e68 --- /dev/null +++ b/test/vitest-light-paths.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { + isCommandsLightTarget, + resolveCommandsLightIncludePattern, +} from "../vitest.commands-light-paths.mjs"; +import { + isPluginSdkLightTarget, + resolvePluginSdkLightIncludePattern, +} from "../vitest.plugin-sdk-paths.mjs"; + +describe("light vitest path routing", () => { + it("maps plugin-sdk allowlist source and test files to sibling light tests", () => { + expect(isPluginSdkLightTarget("src/plugin-sdk/lazy-value.ts")).toBe(true); + expect(isPluginSdkLightTarget("src/plugin-sdk/lazy-value.test.ts")).toBe(true); + expect(resolvePluginSdkLightIncludePattern("src/plugin-sdk/lazy-value.ts")).toBe( + "src/plugin-sdk/lazy-value.test.ts", + ); + expect(resolvePluginSdkLightIncludePattern("src/plugin-sdk/lazy-value.test.ts")).toBe( + "src/plugin-sdk/lazy-value.test.ts", + ); + }); + + it("keeps non-allowlisted plugin-sdk files off the light lane", () => { + expect(isPluginSdkLightTarget("src/plugin-sdk/facade-runtime.ts")).toBe(false); + expect(resolvePluginSdkLightIncludePattern("src/plugin-sdk/facade-runtime.ts")).toBeNull(); + }); + + it("maps commands allowlist source and test files to sibling light tests", () => { + expect(isCommandsLightTarget("src/commands/text-format.ts")).toBe(true); + expect(isCommandsLightTarget("src/commands/text-format.test.ts")).toBe(true); + expect(resolveCommandsLightIncludePattern("src/commands/text-format.ts")).toBe( + "src/commands/text-format.test.ts", + ); + expect(resolveCommandsLightIncludePattern("src/commands/text-format.test.ts")).toBe( + "src/commands/text-format.test.ts", + ); + }); + + it("keeps non-allowlisted commands files off the light lane", () => { + expect(isCommandsLightTarget("src/commands/channels.add.ts")).toBe(false); + expect(resolveCommandsLightIncludePattern("src/commands/channels.add.ts")).toBeNull(); + }); +}); diff --git a/vitest.commands-light-paths.mjs b/vitest.commands-light-paths.mjs index e9b4369dafc..6a1b64a233a 100644 --- a/vitest.commands-light-paths.mjs +++ b/vitest.commands-light-paths.mjs @@ -1,12 +1,53 @@ const normalizeRepoPath = (value) => value.replaceAll("\\", "/"); -export const commandsLightTestFiles = [ - "src/commands/cleanup-utils.test.ts", - "src/commands/dashboard.links.test.ts", - "src/commands/doctor-browser.test.ts", - "src/commands/doctor-gateway-auth-token.test.ts", +const commandsLightEntries = [ + { source: "src/commands/cleanup-utils.ts", test: "src/commands/cleanup-utils.test.ts" }, + { + source: "src/commands/dashboard.links.ts", + test: "src/commands/dashboard.links.test.ts", + }, + { source: "src/commands/doctor-browser.ts", test: "src/commands/doctor-browser.test.ts" }, + { + source: "src/commands/doctor-gateway-auth-token.ts", + test: "src/commands/doctor-gateway-auth-token.test.ts", + }, + { + source: "src/commands/sandbox-formatters.ts", + test: "src/commands/sandbox-formatters.test.ts", + }, + { + source: "src/commands/status-overview-rows.ts", + test: "src/commands/status-overview-rows.test.ts", + }, + { + source: "src/commands/status-overview-surface.ts", + test: "src/commands/status-overview-surface.test.ts", + }, + { + source: "src/commands/status-overview-values.ts", + test: "src/commands/status-overview-values.test.ts", + }, + { source: "src/commands/text-format.ts", test: "src/commands/text-format.test.ts" }, ]; +const commandsLightIncludePatternByFile = new Map( + commandsLightEntries.flatMap(({ source, test }) => [ + [source, test], + [test, test], + ]), +); + +export const commandsLightSourceFiles = commandsLightEntries.map(({ source }) => source); +export const commandsLightTestFiles = commandsLightEntries.map(({ test }) => test); + +export function isCommandsLightTarget(file) { + return commandsLightIncludePatternByFile.has(normalizeRepoPath(file)); +} + export function isCommandsLightTestFile(file) { return commandsLightTestFiles.includes(normalizeRepoPath(file)); } + +export function resolveCommandsLightIncludePattern(file) { + return commandsLightIncludePatternByFile.get(normalizeRepoPath(file)) ?? null; +} diff --git a/vitest.plugin-sdk-paths.mjs b/vitest.plugin-sdk-paths.mjs index db0282eae5f..14624413eb1 100644 --- a/vitest.plugin-sdk-paths.mjs +++ b/vitest.plugin-sdk-paths.mjs @@ -1,14 +1,56 @@ const normalizeRepoPath = (value) => value.replaceAll("\\", "/"); -export const pluginSdkLightTestFiles = [ - "src/plugin-sdk/acp-runtime.test.ts", - "src/plugin-sdk/provider-entry.test.ts", - "src/plugin-sdk/runtime.test.ts", - "src/plugin-sdk/temp-path.test.ts", - "src/plugin-sdk/text-chunking.test.ts", - "src/plugin-sdk/webhook-targets.test.ts", +const pluginSdkLightEntries = [ + { source: "src/plugin-sdk/acp-runtime.ts", test: "src/plugin-sdk/acp-runtime.test.ts" }, + { source: "src/plugin-sdk/allow-from.ts", test: "src/plugin-sdk/allow-from.test.ts" }, + { + source: "src/plugin-sdk/keyed-async-queue.ts", + test: "src/plugin-sdk/keyed-async-queue.test.ts", + }, + { source: "src/plugin-sdk/lazy-value.ts", test: "src/plugin-sdk/lazy-value.test.ts" }, + { + source: "src/plugin-sdk/persistent-dedupe.ts", + test: "src/plugin-sdk/persistent-dedupe.test.ts", + }, + { source: "src/plugin-sdk/provider-entry.ts", test: "src/plugin-sdk/provider-entry.test.ts" }, + { + source: "src/plugin-sdk/provider-model-shared.ts", + test: "src/plugin-sdk/provider-model-shared.test.ts", + }, + { source: "src/plugin-sdk/provider-tools.ts", test: "src/plugin-sdk/provider-tools.test.ts" }, + { + source: "src/plugin-sdk/status-helpers.ts", + test: "src/plugin-sdk/status-helpers.test.ts", + }, + { source: "src/plugin-sdk/temp-path.ts", test: "src/plugin-sdk/temp-path.test.ts" }, + { + source: "src/plugin-sdk/text-chunking.ts", + test: "src/plugin-sdk/text-chunking.test.ts", + }, + { + source: "src/plugin-sdk/webhook-targets.ts", + test: "src/plugin-sdk/webhook-targets.test.ts", + }, ]; +const pluginSdkLightIncludePatternByFile = new Map( + pluginSdkLightEntries.flatMap(({ source, test }) => [ + [source, test], + [test, test], + ]), +); + +export const pluginSdkLightSourceFiles = pluginSdkLightEntries.map(({ source }) => source); +export const pluginSdkLightTestFiles = pluginSdkLightEntries.map(({ test }) => test); + +export function isPluginSdkLightTarget(file) { + return pluginSdkLightIncludePatternByFile.has(normalizeRepoPath(file)); +} + export function isPluginSdkLightTestFile(file) { return pluginSdkLightTestFiles.includes(normalizeRepoPath(file)); } + +export function resolvePluginSdkLightIncludePattern(file) { + return pluginSdkLightIncludePatternByFile.get(normalizeRepoPath(file)) ?? null; +}