refactor: unwrap ConfigPlugin namespace to flat exports + self-reexport (#22876)

This commit is contained in:
Kit Langton
2026-04-16 16:59:17 -04:00
committed by GitHub
parent 86c54c5acc
commit 32548bcb4a
+28 -28
View File
@@ -4,28 +4,27 @@ import { pathToFileURL } from "url"
import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared" import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared"
import path from "path" import path from "path"
export namespace ConfigPlugin { const Options = z.record(z.string(), z.unknown())
const Options = z.record(z.string(), z.unknown()) export type Options = z.infer<typeof Options>
export type Options = z.infer<typeof Options>
// Spec is the user-config value: either just a plugin identifier, or the identifier plus inline options. // Spec is the user-config value: either just a plugin identifier, or the identifier plus inline options.
// It answers "what should we load?" but says nothing about where that value came from. // It answers "what should we load?" but says nothing about where that value came from.
export const Spec = z.union([z.string(), z.tuple([z.string(), Options])]) export const Spec = z.union([z.string(), z.tuple([z.string(), Options])])
export type Spec = z.infer<typeof Spec> export type Spec = z.infer<typeof Spec>
export type Scope = "global" | "local" export type Scope = "global" | "local"
// Origin keeps the original config provenance attached to a spec. // Origin keeps the original config provenance attached to a spec.
// After multiple config files are merged, callers still need to know which file declared the plugin // After multiple config files are merged, callers still need to know which file declared the plugin
// and whether it should behave like a global or project-local plugin. // and whether it should behave like a global or project-local plugin.
export type Origin = { export type Origin = {
spec: Spec spec: Spec
source: string source: string
scope: Scope scope: Scope
} }
export async function load(dir: string) { export async function load(dir: string) {
const plugins: ConfigPlugin.Spec[] = [] const plugins: Spec[] = []
for (const item of await Glob.scan("{plugin,plugins}/*.{ts,js}", { for (const item of await Glob.scan("{plugin,plugins}/*.{ts,js}", {
cwd: dir, cwd: dir,
@@ -36,19 +35,19 @@ export namespace ConfigPlugin {
plugins.push(pathToFileURL(item).href) plugins.push(pathToFileURL(item).href)
} }
return plugins return plugins
} }
export function pluginSpecifier(plugin: Spec): string { export function pluginSpecifier(plugin: Spec): string {
return Array.isArray(plugin) ? plugin[0] : plugin return Array.isArray(plugin) ? plugin[0] : plugin
} }
export function pluginOptions(plugin: Spec): Options | undefined { export function pluginOptions(plugin: Spec): Options | undefined {
return Array.isArray(plugin) ? plugin[1] : undefined return Array.isArray(plugin) ? plugin[1] : undefined
} }
// Path-like specs are resolved relative to the config file that declared them so merges later on do not // Path-like specs are resolved relative to the config file that declared them so merges later on do not
// accidentally reinterpret `./plugin.ts` relative to some other directory. // accidentally reinterpret `./plugin.ts` relative to some other directory.
export async function resolvePluginSpec(plugin: Spec, configFilepath: string): Promise<Spec> { export async function resolvePluginSpec(plugin: Spec, configFilepath: string): Promise<Spec> {
const spec = pluginSpecifier(plugin) const spec = pluginSpecifier(plugin)
if (!isPathPluginSpec(spec)) return plugin if (!isPathPluginSpec(spec)) return plugin
@@ -63,11 +62,11 @@ export namespace ConfigPlugin {
if (Array.isArray(plugin)) return [resolved, plugin[1]] if (Array.isArray(plugin)) return [resolved, plugin[1]]
return resolved return resolved
} }
// Dedupe on the load identity (package name for npm specs, exact file URL for local specs), but keep the // Dedupe on the load identity (package name for npm specs, exact file URL for local specs), but keep the
// full Origin so downstream code still knows which config file won and where follow-up writes should go. // full Origin so downstream code still knows which config file won and where follow-up writes should go.
export function deduplicatePluginOrigins(plugins: Origin[]): Origin[] { export function deduplicatePluginOrigins(plugins: Origin[]): Origin[] {
const seen = new Set<string>() const seen = new Set<string>()
const list: Origin[] = [] const list: Origin[] = []
@@ -80,5 +79,6 @@ export namespace ConfigPlugin {
} }
return list.toReversed() return list.toReversed()
}
} }
export * as ConfigPlugin from "./plugin"