mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
fix(cli): properly resolve subdir plugin installs
This commit is contained in:
parent
a7dca22667
commit
166b9c87d8
@ -8,6 +8,7 @@ import { PluginTypes } from "../types"
|
||||
import {
|
||||
PluginManifest,
|
||||
PluginJsonEntry,
|
||||
PluginSource,
|
||||
QuartzPluginsJson,
|
||||
LayoutConfig,
|
||||
PluginLayoutDeclaration,
|
||||
@ -50,8 +51,12 @@ function readPluginsJson(): QuartzPluginsJson | null {
|
||||
return JSON.parse(raw) as QuartzPluginsJson
|
||||
}
|
||||
|
||||
function extractPluginName(source: string): string {
|
||||
// Local file paths: use directory basename
|
||||
function extractPluginName(source: PluginSource): string {
|
||||
if (typeof source === "object" && source !== null) {
|
||||
if (source.name) return source.name
|
||||
return extractPluginName(source.repo)
|
||||
}
|
||||
|
||||
if (isLocalSource(source)) {
|
||||
return path.basename(source.replace(/[\/]+$/, ""))
|
||||
}
|
||||
@ -69,6 +74,19 @@ function extractPluginName(source: string): string {
|
||||
return source
|
||||
}
|
||||
|
||||
function formatSourceDisplay(source: PluginSource): string {
|
||||
if (typeof source === "string") return source
|
||||
const parts = [source.repo]
|
||||
if (source.subdir) parts.push(`(subdir: ${source.subdir})`)
|
||||
if (source.ref) parts.push(`(ref: ${source.ref})`)
|
||||
return parts.join(" ")
|
||||
}
|
||||
|
||||
function sourceKey(source: PluginSource): string {
|
||||
if (typeof source === "string") return source
|
||||
return JSON.stringify(source)
|
||||
}
|
||||
|
||||
interface DependencyValidationResult {
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
@ -84,13 +102,13 @@ function validateDependencies(
|
||||
const sourceToEntry = new Map<string, PluginJsonEntry>()
|
||||
const nameToSource = new Map<string, string>()
|
||||
for (const entry of entries) {
|
||||
sourceToEntry.set(entry.source, entry)
|
||||
nameToSource.set(extractPluginName(entry.source), entry.source)
|
||||
sourceToEntry.set(sourceKey(entry.source), entry)
|
||||
nameToSource.set(extractPluginName(entry.source), sourceKey(entry.source))
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.enabled) continue
|
||||
const manifest = manifests.get(entry.source)
|
||||
const manifest = manifests.get(sourceKey(entry.source))
|
||||
if (!manifest?.dependencies?.length) continue
|
||||
|
||||
const pluginName = manifest.displayName || extractPluginName(entry.source)
|
||||
@ -126,12 +144,11 @@ function validateDependencies(
|
||||
}
|
||||
}
|
||||
|
||||
// Circular dependency detection
|
||||
const graph = new Map<string, string[]>()
|
||||
for (const entry of entries) {
|
||||
const manifest = manifests.get(entry.source)
|
||||
const manifest = manifests.get(sourceKey(entry.source))
|
||||
if (manifest?.dependencies?.length) {
|
||||
graph.set(entry.source, manifest.dependencies)
|
||||
graph.set(sourceKey(entry.source), manifest.dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +186,7 @@ function validateDependencies(
|
||||
return { errors, warnings }
|
||||
}
|
||||
|
||||
async function resolvePluginManifest(source: string): Promise<PluginManifest | null> {
|
||||
async function resolvePluginManifest(source: PluginSource): Promise<PluginManifest | null> {
|
||||
try {
|
||||
const gitSpec = parsePluginSource(source)
|
||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
||||
@ -180,7 +197,7 @@ async function resolvePluginManifest(source: string): Promise<PluginManifest | n
|
||||
}
|
||||
}
|
||||
|
||||
async function readManifestFromPackageJson(source: string): Promise<PluginManifest | null> {
|
||||
async function readManifestFromPackageJson(source: PluginSource): Promise<PluginManifest | null> {
|
||||
try {
|
||||
const gitSpec = parsePluginSource(source)
|
||||
const pluginDir = path.join(process.cwd(), ".quartz", "plugins", gitSpec.name)
|
||||
@ -213,7 +230,7 @@ async function readManifestFromPackageJson(source: string): Promise<PluginManife
|
||||
}
|
||||
}
|
||||
|
||||
async function getManifest(source: string): Promise<PluginManifest | null> {
|
||||
async function getManifest(source: PluginSource): Promise<PluginManifest | null> {
|
||||
// Try package.json quartz field first (preferred), then fall back to manifest.ts export
|
||||
return (await readManifestFromPackageJson(source)) ?? (await resolvePluginManifest(source))
|
||||
}
|
||||
@ -249,7 +266,7 @@ export async function loadQuartzConfig(
|
||||
} catch (err) {
|
||||
console.error(
|
||||
styleText("red", `✗`) +
|
||||
` Failed to install plugin: ${styleText("yellow", entry.source)}\n` +
|
||||
` Failed to install plugin: ${styleText("yellow", formatSourceDisplay(entry.source))}\n` +
|
||||
` ${err instanceof Error ? err.message : String(err)}`,
|
||||
)
|
||||
}
|
||||
@ -264,12 +281,12 @@ export async function loadQuartzConfig(
|
||||
try {
|
||||
const manifest = await getManifest(entry.source)
|
||||
if (manifest) {
|
||||
manifests.set(entry.source, manifest)
|
||||
manifests.set(sourceKey(entry.source), manifest)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
styleText("red", `✗`) +
|
||||
` Failed to load manifest: ${styleText("yellow", entry.source)}\n` +
|
||||
` Failed to load manifest: ${styleText("yellow", formatSourceDisplay(entry.source))}\n` +
|
||||
` ${err instanceof Error ? err.message : String(err)}`,
|
||||
)
|
||||
}
|
||||
@ -296,7 +313,7 @@ export async function loadQuartzConfig(
|
||||
const pageTypes: { entry: PluginJsonEntry; manifest: PluginManifest | undefined }[] = []
|
||||
|
||||
for (const entry of enabledEntries) {
|
||||
const manifest = manifests.get(entry.source)
|
||||
const manifest = manifests.get(sourceKey(entry.source))
|
||||
const category = manifest?.category
|
||||
// Resolve processing categories: for array categories (e.g. ["transformer", "pageType", "component"]),
|
||||
// push the plugin into ALL matching processing category buckets.
|
||||
@ -683,7 +700,8 @@ function buildLayoutForEntries(
|
||||
|
||||
// Look up component from registry
|
||||
const registered =
|
||||
componentRegistry.get(name) ?? componentRegistry.get(`${entry.source}/${name}`)
|
||||
componentRegistry.get(name) ??
|
||||
componentRegistry.get(`${formatSourceDisplay(entry.source)}/${name}`)
|
||||
if (!registered) {
|
||||
// Try common naming patterns
|
||||
const pascalName = name
|
||||
|
||||
@ -5,6 +5,7 @@ import git from "isomorphic-git"
|
||||
import http from "isomorphic-git/http/node"
|
||||
import { styleText } from "util"
|
||||
import { pathToFileURL } from "url"
|
||||
import { PluginSource } from "./types"
|
||||
|
||||
/**
|
||||
* Convert an absolute filesystem path to a file:// URL string for use with dynamic import().
|
||||
@ -40,7 +41,10 @@ const PLUGINS_CACHE_DIR = path.join(process.cwd(), ".quartz", "plugins")
|
||||
* Check if a source string refers to a local file path.
|
||||
* Local sources start with ./, ../, / or a Windows drive letter (e.g. C:\).
|
||||
*/
|
||||
export function isLocalSource(source: string): boolean {
|
||||
export function isLocalSource(source: PluginSource): boolean {
|
||||
if (typeof source === "object") {
|
||||
return isLocalSource(source.repo)
|
||||
}
|
||||
if (source.startsWith("./") || source.startsWith("../") || source.startsWith("/")) {
|
||||
return true
|
||||
}
|
||||
@ -60,7 +64,22 @@ export function isLocalSource(source: string): boolean {
|
||||
* - "git+https://..." -> direct git URL
|
||||
* - "https://github.com/..." -> direct https URL
|
||||
*/
|
||||
export function parsePluginSource(source: string): GitPluginSpec {
|
||||
export function parsePluginSource(source: PluginSource): GitPluginSpec {
|
||||
if (typeof source === "object" && source !== null) {
|
||||
const url = source.repo
|
||||
const subdir = source.subdir
|
||||
const ref = source.ref
|
||||
|
||||
if (isLocalSource(url)) {
|
||||
const resolved = path.resolve(url)
|
||||
const name = source.name ?? path.basename(resolved)
|
||||
return { name, repo: resolved, local: true, subdir }
|
||||
}
|
||||
|
||||
const name = source.name ?? extractRepoName(url)
|
||||
return { name, repo: url, ref: ref || undefined, subdir }
|
||||
}
|
||||
|
||||
// Handle local paths
|
||||
if (isLocalSource(source)) {
|
||||
const resolved = path.resolve(source)
|
||||
|
||||
@ -141,9 +141,20 @@ export interface PluginLayoutDeclaration {
|
||||
}
|
||||
}
|
||||
|
||||
/** Object form of a plugin source (for monorepo / advanced config) */
|
||||
export interface PluginSourceObject {
|
||||
repo: string
|
||||
subdir?: string
|
||||
ref?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
/** A plugin source can be a string shorthand or an object with additional fields */
|
||||
export type PluginSource = string | PluginSourceObject
|
||||
|
||||
/** A single plugin entry in quartz.config.yaml */
|
||||
export interface PluginJsonEntry {
|
||||
source: string
|
||||
source: PluginSource
|
||||
enabled: boolean
|
||||
options?: Record<string, unknown>
|
||||
order?: number
|
||||
|
||||
Loading…
Reference in New Issue
Block a user