fix: improved plugin type resolution
This commit is contained in:
@@ -203,6 +203,23 @@ function findPluginByPackageName(packageName) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PLUGIN_TYPE_PATTERN =
|
||||||
|
/Quartz(?:Emitter|Transformer|Filter|PageType)Plugin|QuartzComponentConstructor|\(.*\)\s*=>\s*QuartzComponent\b/
|
||||||
|
|
||||||
|
function resolveOriginalName(exportName, dtsContent) {
|
||||||
|
const aliasPattern = new RegExp(`(\\w+)\\s+as\\s+${exportName}\\b`)
|
||||||
|
const match = dtsContent.match(aliasPattern)
|
||||||
|
return match ? match[1] : exportName
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOverridableExport(name, dtsContent) {
|
||||||
|
const declName = resolveOriginalName(name, dtsContent)
|
||||||
|
const declPattern = new RegExp(`declare\\s+const\\s+${declName}\\s*:\\s*(.+?)(?:;|$)`, "m")
|
||||||
|
const match = dtsContent.match(declPattern)
|
||||||
|
if (!match) return false
|
||||||
|
return PLUGIN_TYPE_PATTERN.test(match[1])
|
||||||
|
}
|
||||||
|
|
||||||
function parseExportsFromDts(content) {
|
function parseExportsFromDts(content) {
|
||||||
const exports = []
|
const exports = []
|
||||||
const exportMatches = content.matchAll(/export\s*{\s*([^}]+)\s*}(?:\s*from\s*['"]([^'"]+)['"])?/g)
|
const exportMatches = content.matchAll(/export\s*{\s*([^}]+)\s*}(?:\s*from\s*['"]([^'"]+)['"])?/g)
|
||||||
@@ -232,14 +249,16 @@ function parseExportsFromDts(content) {
|
|||||||
async function regeneratePluginIndex() {
|
async function regeneratePluginIndex() {
|
||||||
if (!fs.existsSync(PLUGINS_DIR)) return
|
if (!fs.existsSync(PLUGINS_DIR)) return
|
||||||
|
|
||||||
const plugins = fs.readdirSync(PLUGINS_DIR).filter((name) => {
|
const pluginDirs = fs.readdirSync(PLUGINS_DIR).filter((name) => {
|
||||||
const pluginPath = path.join(PLUGINS_DIR, name)
|
const pluginPath = path.join(PLUGINS_DIR, name)
|
||||||
return fs.statSync(pluginPath).isDirectory()
|
return fs.statSync(pluginPath).isDirectory()
|
||||||
})
|
})
|
||||||
|
|
||||||
const exports = []
|
// Phase 1: Collect all exports per plugin, detect conflicts
|
||||||
|
const pluginExports = new Map()
|
||||||
|
const nameCount = new Map()
|
||||||
|
|
||||||
for (const pluginName of plugins) {
|
for (const pluginName of pluginDirs) {
|
||||||
const pluginDir = path.join(PLUGINS_DIR, pluginName)
|
const pluginDir = path.join(PLUGINS_DIR, pluginName)
|
||||||
const distIndex = path.join(pluginDir, "dist", "index.d.ts")
|
const distIndex = path.join(pluginDir, "dist", "index.d.ts")
|
||||||
|
|
||||||
@@ -247,21 +266,88 @@ async function regeneratePluginIndex() {
|
|||||||
|
|
||||||
const dtsContent = fs.readFileSync(distIndex, "utf-8")
|
const dtsContent = fs.readFileSync(distIndex, "utf-8")
|
||||||
const exportedNames = parseExportsFromDts(dtsContent)
|
const exportedNames = parseExportsFromDts(dtsContent)
|
||||||
|
const named = exportedNames.filter((e) => !e.startsWith("type "))
|
||||||
|
const types = exportedNames.filter((e) => e.startsWith("type ")).map((e) => e.slice(5))
|
||||||
|
|
||||||
if (exportedNames.length > 0) {
|
const overridable = named.filter((n) => isOverridableExport(n, dtsContent))
|
||||||
const namedExports = exportedNames.filter((e) => !e.startsWith("type "))
|
const passthrough = named.filter((n) => !isOverridableExport(n, dtsContent))
|
||||||
const typeExports = exportedNames.filter((e) => e.startsWith("type ")).map((e) => e.slice(5))
|
|
||||||
|
|
||||||
if (namedExports.length > 0) {
|
if (overridable.length > 0 || passthrough.length > 0 || types.length > 0) {
|
||||||
exports.push(`export { ${namedExports.join(", ")} } from "./${pluginName}"`)
|
pluginExports.set(pluginName, { overridable, passthrough, types })
|
||||||
}
|
for (const n of [...overridable, ...passthrough]) {
|
||||||
if (typeExports.length > 0) {
|
nameCount.set(n, (nameCount.get(n) ?? 0) + 1)
|
||||||
exports.push(`export type { ${typeExports.join(", ")} } from "./${pluginName}"`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexContent = exports.join("\n") + "\n"
|
// Phase 2: Generate index with registry import, plugin map, and conditional top-level exports
|
||||||
|
const lines = []
|
||||||
|
|
||||||
|
lines.push(`import { componentRegistry } from "../../quartz/components/registry"`)
|
||||||
|
lines.push("")
|
||||||
|
|
||||||
|
// Type re-exports
|
||||||
|
for (const [pluginName, { types }] of pluginExports) {
|
||||||
|
if (types.length > 0) {
|
||||||
|
lines.push(`export type { ${types.join(", ")} } from "./${pluginName}"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct re-exports for non-overridable values (constants, utility functions, etc.)
|
||||||
|
for (const [pluginName, { passthrough }] of pluginExports) {
|
||||||
|
if (passthrough.length === 0) continue
|
||||||
|
const unique = passthrough.filter((n) => (nameCount.get(n) ?? 0) === 1)
|
||||||
|
if (unique.length > 0) {
|
||||||
|
lines.push(`export { ${unique.join(", ")} } from "./${pluginName}"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push("")
|
||||||
|
|
||||||
|
// Generate the plugins map with override wrappers (overridable exports only)
|
||||||
|
lines.push(
|
||||||
|
`export const plugins: Record<string, Record<string, (...args: unknown[]) => void>> = {`,
|
||||||
|
)
|
||||||
|
for (const [pluginName, { overridable }] of pluginExports) {
|
||||||
|
if (overridable.length === 0) continue
|
||||||
|
const escapedName = pluginName.replace(/"/g, '\\"')
|
||||||
|
lines.push(` "${escapedName}": {`)
|
||||||
|
for (const n of overridable) {
|
||||||
|
lines.push(
|
||||||
|
` ${n}: (...args: unknown[]) => { componentRegistry.setOptionOverrides("${escapedName}", args[0] as Record<string, unknown>); },`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
lines.push(` },`)
|
||||||
|
}
|
||||||
|
lines.push(`}`)
|
||||||
|
lines.push("")
|
||||||
|
|
||||||
|
// Top-level exports for overridable names: alias to the plugins map wrapper
|
||||||
|
for (const [pluginName, { overridable }] of pluginExports) {
|
||||||
|
if (overridable.length === 0) continue
|
||||||
|
|
||||||
|
const unique = overridable.filter((n) => (nameCount.get(n) ?? 0) === 1)
|
||||||
|
const conflicting = overridable.filter((n) => (nameCount.get(n) ?? 0) > 1)
|
||||||
|
|
||||||
|
if (unique.length > 0) {
|
||||||
|
const escapedName = pluginName.replace(/"/g, '\\"')
|
||||||
|
for (const n of unique) {
|
||||||
|
lines.push(`export const ${n} = plugins["${escapedName}"].${n}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conflicting.length > 0) {
|
||||||
|
for (const n of conflicting) {
|
||||||
|
console.warn(
|
||||||
|
styleText("yellow", `⚠`),
|
||||||
|
`Export "${n}" conflicts across plugins — use plugins["${pluginName}"].${n} in quartz.ts`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("")
|
||||||
|
|
||||||
|
const indexContent = lines.join("\n")
|
||||||
const indexPath = path.join(PLUGINS_DIR, "index.ts")
|
const indexPath = path.join(PLUGINS_DIR, "index.ts")
|
||||||
fs.writeFileSync(indexPath, indexContent)
|
fs.writeFileSync(indexPath, indexContent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -914,7 +914,10 @@ export async function regeneratePluginIndex(options: { verbose?: boolean } = {})
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Phase 1: Collect all exports per plugin, detect conflicts
|
// Phase 1: Collect all exports per plugin, detect conflicts
|
||||||
const pluginExports = new Map<string, { named: string[]; types: string[] }>()
|
const pluginExports = new Map<
|
||||||
|
string,
|
||||||
|
{ overridable: string[]; passthrough: string[]; types: string[] }
|
||||||
|
>()
|
||||||
const nameCount = new Map<string, number>()
|
const nameCount = new Map<string, number>()
|
||||||
|
|
||||||
for (const pluginName of pluginDirs) {
|
for (const pluginName of pluginDirs) {
|
||||||
@@ -933,9 +936,12 @@ export async function regeneratePluginIndex(options: { verbose?: boolean } = {})
|
|||||||
const named = exportedNames.filter((e) => !e.startsWith("type "))
|
const named = exportedNames.filter((e) => !e.startsWith("type "))
|
||||||
const types = exportedNames.filter((e) => e.startsWith("type ")).map((e) => e.slice(5))
|
const types = exportedNames.filter((e) => e.startsWith("type ")).map((e) => e.slice(5))
|
||||||
|
|
||||||
if (named.length > 0 || types.length > 0) {
|
const overridable = named.filter((n) => isOverridableExport(n, dtsContent))
|
||||||
pluginExports.set(pluginName, { named, types })
|
const passthrough = named.filter((n) => !isOverridableExport(n, dtsContent))
|
||||||
for (const n of named) {
|
|
||||||
|
if (overridable.length > 0 || passthrough.length > 0 || types.length > 0) {
|
||||||
|
pluginExports.set(pluginName, { overridable, passthrough, types })
|
||||||
|
for (const n of [...overridable, ...passthrough]) {
|
||||||
nameCount.set(n, (nameCount.get(n) ?? 0) + 1)
|
nameCount.set(n, (nameCount.get(n) ?? 0) + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -953,17 +959,26 @@ export async function regeneratePluginIndex(options: { verbose?: boolean } = {})
|
|||||||
lines.push(`export type { ${types.join(", ")} } from "./${pluginName}"`)
|
lines.push(`export type { ${types.join(", ")} } from "./${pluginName}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct re-exports for non-overridable values (constants, utility functions, etc.)
|
||||||
|
for (const [pluginName, { passthrough }] of pluginExports) {
|
||||||
|
if (passthrough.length === 0) continue
|
||||||
|
const unique = passthrough.filter((n) => (nameCount.get(n) ?? 0) === 1)
|
||||||
|
if (unique.length > 0) {
|
||||||
|
lines.push(`export { ${unique.join(", ")} } from "./${pluginName}"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
lines.push("")
|
lines.push("")
|
||||||
|
|
||||||
// Generate the plugins map with override wrappers
|
// Generate the plugins map with override wrappers (overridable exports only)
|
||||||
lines.push(
|
lines.push(
|
||||||
`export const plugins: Record<string, Record<string, (...args: unknown[]) => void>> = {`,
|
`export const plugins: Record<string, Record<string, (...args: unknown[]) => void>> = {`,
|
||||||
)
|
)
|
||||||
for (const [pluginName, { named }] of pluginExports) {
|
for (const [pluginName, { overridable }] of pluginExports) {
|
||||||
if (named.length === 0) continue
|
if (overridable.length === 0) continue
|
||||||
const escapedName = pluginName.replace(/"/g, '\\"')
|
const escapedName = pluginName.replace(/"/g, '\\"')
|
||||||
lines.push(` "${escapedName}": {`)
|
lines.push(` "${escapedName}": {`)
|
||||||
for (const n of named) {
|
for (const n of overridable) {
|
||||||
lines.push(
|
lines.push(
|
||||||
` ${n}: (...args: unknown[]) => { componentRegistry.setOptionOverrides("${escapedName}", args[0] as Record<string, unknown>); },`,
|
` ${n}: (...args: unknown[]) => { componentRegistry.setOptionOverrides("${escapedName}", args[0] as Record<string, unknown>); },`,
|
||||||
)
|
)
|
||||||
@@ -973,12 +988,12 @@ export async function regeneratePluginIndex(options: { verbose?: boolean } = {})
|
|||||||
lines.push(`}`)
|
lines.push(`}`)
|
||||||
lines.push("")
|
lines.push("")
|
||||||
|
|
||||||
// Top-level exports: only for non-conflicting names
|
// Top-level exports for overridable names: alias to the plugins map wrapper
|
||||||
for (const [pluginName, { named }] of pluginExports) {
|
for (const [pluginName, { overridable }] of pluginExports) {
|
||||||
if (named.length === 0) continue
|
if (overridable.length === 0) continue
|
||||||
|
|
||||||
const unique = named.filter((n) => (nameCount.get(n) ?? 0) === 1)
|
const unique = overridable.filter((n) => (nameCount.get(n) ?? 0) === 1)
|
||||||
const conflicting = named.filter((n) => (nameCount.get(n) ?? 0) > 1)
|
const conflicting = overridable.filter((n) => (nameCount.get(n) ?? 0) > 1)
|
||||||
|
|
||||||
if (unique.length > 0) {
|
if (unique.length > 0) {
|
||||||
const escapedName = pluginName.replace(/"/g, '\\"')
|
const escapedName = pluginName.replace(/"/g, '\\"')
|
||||||
@@ -1014,6 +1029,23 @@ export async function regeneratePluginIndex(options: { verbose?: boolean } = {})
|
|||||||
|
|
||||||
const INTERNAL_EXPORTS = new Set(["manifest", "default"])
|
const INTERNAL_EXPORTS = new Set(["manifest", "default"])
|
||||||
|
|
||||||
|
const PLUGIN_TYPE_PATTERN =
|
||||||
|
/Quartz(?:Emitter|Transformer|Filter|PageType)Plugin|QuartzComponentConstructor|\(.*\)\s*=>\s*QuartzComponent\b/
|
||||||
|
|
||||||
|
function resolveOriginalName(exportName: string, dtsContent: string): string {
|
||||||
|
const aliasPattern = new RegExp(`(\\w+)\\s+as\\s+${exportName}\\b`)
|
||||||
|
const match = dtsContent.match(aliasPattern)
|
||||||
|
return match ? match[1] : exportName
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOverridableExport(name: string, dtsContent: string): boolean {
|
||||||
|
const declName = resolveOriginalName(name, dtsContent)
|
||||||
|
const declPattern = new RegExp(`declare\\s+const\\s+${declName}\\s*:\\s*(.+?)(?:;|$)`, "m")
|
||||||
|
const match = dtsContent.match(declPattern)
|
||||||
|
if (!match) return false
|
||||||
|
return PLUGIN_TYPE_PATTERN.test(match[1])
|
||||||
|
}
|
||||||
|
|
||||||
function parseExportsFromDts(content: string): string[] {
|
function parseExportsFromDts(content: string): string[] {
|
||||||
const exports: string[] = []
|
const exports: string[] = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user