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 {
|
import {
|
||||||
PluginManifest,
|
PluginManifest,
|
||||||
PluginJsonEntry,
|
PluginJsonEntry,
|
||||||
|
PluginSource,
|
||||||
QuartzPluginsJson,
|
QuartzPluginsJson,
|
||||||
LayoutConfig,
|
LayoutConfig,
|
||||||
PluginLayoutDeclaration,
|
PluginLayoutDeclaration,
|
||||||
@ -50,8 +51,12 @@ function readPluginsJson(): QuartzPluginsJson | null {
|
|||||||
return JSON.parse(raw) as QuartzPluginsJson
|
return JSON.parse(raw) as QuartzPluginsJson
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPluginName(source: string): string {
|
function extractPluginName(source: PluginSource): string {
|
||||||
// Local file paths: use directory basename
|
if (typeof source === "object" && source !== null) {
|
||||||
|
if (source.name) return source.name
|
||||||
|
return extractPluginName(source.repo)
|
||||||
|
}
|
||||||
|
|
||||||
if (isLocalSource(source)) {
|
if (isLocalSource(source)) {
|
||||||
return path.basename(source.replace(/[\/]+$/, ""))
|
return path.basename(source.replace(/[\/]+$/, ""))
|
||||||
}
|
}
|
||||||
@ -69,6 +74,19 @@ function extractPluginName(source: string): string {
|
|||||||
return source
|
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 {
|
interface DependencyValidationResult {
|
||||||
errors: string[]
|
errors: string[]
|
||||||
warnings: string[]
|
warnings: string[]
|
||||||
@ -84,13 +102,13 @@ function validateDependencies(
|
|||||||
const sourceToEntry = new Map<string, PluginJsonEntry>()
|
const sourceToEntry = new Map<string, PluginJsonEntry>()
|
||||||
const nameToSource = new Map<string, string>()
|
const nameToSource = new Map<string, string>()
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
sourceToEntry.set(entry.source, entry)
|
sourceToEntry.set(sourceKey(entry.source), entry)
|
||||||
nameToSource.set(extractPluginName(entry.source), entry.source)
|
nameToSource.set(extractPluginName(entry.source), sourceKey(entry.source))
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.enabled) continue
|
if (!entry.enabled) continue
|
||||||
const manifest = manifests.get(entry.source)
|
const manifest = manifests.get(sourceKey(entry.source))
|
||||||
if (!manifest?.dependencies?.length) continue
|
if (!manifest?.dependencies?.length) continue
|
||||||
|
|
||||||
const pluginName = manifest.displayName || extractPluginName(entry.source)
|
const pluginName = manifest.displayName || extractPluginName(entry.source)
|
||||||
@ -126,12 +144,11 @@ function validateDependencies(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circular dependency detection
|
|
||||||
const graph = new Map<string, string[]>()
|
const graph = new Map<string, string[]>()
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const manifest = manifests.get(entry.source)
|
const manifest = manifests.get(sourceKey(entry.source))
|
||||||
if (manifest?.dependencies?.length) {
|
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 }
|
return { errors, warnings }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolvePluginManifest(source: string): Promise<PluginManifest | null> {
|
async function resolvePluginManifest(source: PluginSource): Promise<PluginManifest | null> {
|
||||||
try {
|
try {
|
||||||
const gitSpec = parsePluginSource(source)
|
const gitSpec = parsePluginSource(source)
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
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 {
|
try {
|
||||||
const gitSpec = parsePluginSource(source)
|
const gitSpec = parsePluginSource(source)
|
||||||
const pluginDir = path.join(process.cwd(), ".quartz", "plugins", gitSpec.name)
|
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
|
// Try package.json quartz field first (preferred), then fall back to manifest.ts export
|
||||||
return (await readManifestFromPackageJson(source)) ?? (await resolvePluginManifest(source))
|
return (await readManifestFromPackageJson(source)) ?? (await resolvePluginManifest(source))
|
||||||
}
|
}
|
||||||
@ -249,7 +266,7 @@ export async function loadQuartzConfig(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
styleText("red", `✗`) +
|
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)}`,
|
` ${err instanceof Error ? err.message : String(err)}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -264,12 +281,12 @@ export async function loadQuartzConfig(
|
|||||||
try {
|
try {
|
||||||
const manifest = await getManifest(entry.source)
|
const manifest = await getManifest(entry.source)
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
manifests.set(entry.source, manifest)
|
manifests.set(sourceKey(entry.source), manifest)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
styleText("red", `✗`) +
|
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)}`,
|
` ${err instanceof Error ? err.message : String(err)}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -296,7 +313,7 @@ export async function loadQuartzConfig(
|
|||||||
const pageTypes: { entry: PluginJsonEntry; manifest: PluginManifest | undefined }[] = []
|
const pageTypes: { entry: PluginJsonEntry; manifest: PluginManifest | undefined }[] = []
|
||||||
|
|
||||||
for (const entry of enabledEntries) {
|
for (const entry of enabledEntries) {
|
||||||
const manifest = manifests.get(entry.source)
|
const manifest = manifests.get(sourceKey(entry.source))
|
||||||
const category = manifest?.category
|
const category = manifest?.category
|
||||||
// Resolve processing categories: for array categories (e.g. ["transformer", "pageType", "component"]),
|
// Resolve processing categories: for array categories (e.g. ["transformer", "pageType", "component"]),
|
||||||
// push the plugin into ALL matching processing category buckets.
|
// push the plugin into ALL matching processing category buckets.
|
||||||
@ -683,7 +700,8 @@ function buildLayoutForEntries(
|
|||||||
|
|
||||||
// Look up component from registry
|
// Look up component from registry
|
||||||
const registered =
|
const registered =
|
||||||
componentRegistry.get(name) ?? componentRegistry.get(`${entry.source}/${name}`)
|
componentRegistry.get(name) ??
|
||||||
|
componentRegistry.get(`${formatSourceDisplay(entry.source)}/${name}`)
|
||||||
if (!registered) {
|
if (!registered) {
|
||||||
// Try common naming patterns
|
// Try common naming patterns
|
||||||
const pascalName = name
|
const pascalName = name
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import git from "isomorphic-git"
|
|||||||
import http from "isomorphic-git/http/node"
|
import http from "isomorphic-git/http/node"
|
||||||
import { styleText } from "util"
|
import { styleText } from "util"
|
||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
|
import { PluginSource } from "./types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an absolute filesystem path to a file:// URL string for use with dynamic import().
|
* 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.
|
* Check if a source string refers to a local file path.
|
||||||
* Local sources start with ./, ../, / or a Windows drive letter (e.g. C:\).
|
* 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("/")) {
|
if (source.startsWith("./") || source.startsWith("../") || source.startsWith("/")) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -60,7 +64,22 @@ export function isLocalSource(source: string): boolean {
|
|||||||
* - "git+https://..." -> direct git URL
|
* - "git+https://..." -> direct git URL
|
||||||
* - "https://github.com/..." -> direct https 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
|
// Handle local paths
|
||||||
if (isLocalSource(source)) {
|
if (isLocalSource(source)) {
|
||||||
const resolved = path.resolve(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 */
|
/** A single plugin entry in quartz.config.yaml */
|
||||||
export interface PluginJsonEntry {
|
export interface PluginJsonEntry {
|
||||||
source: string
|
source: PluginSource
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
options?: Record<string, unknown>
|
options?: Record<string, unknown>
|
||||||
order?: number
|
order?: number
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user