fix(cli): extended options for plugin sources

This commit is contained in:
saberzero1 2026-03-18 01:12:38 +01:00
parent b2ae694b9a
commit 7b85aa3464
No known key found for this signature in database
4 changed files with 260 additions and 91 deletions

View File

@ -83,32 +83,73 @@ export function writeLockfile(lockfile) {
fs.writeFileSync(LOCKFILE_PATH, JSON.stringify(lockfile, null, 2) + "\n") fs.writeFileSync(LOCKFILE_PATH, JSON.stringify(lockfile, null, 2) + "\n")
} }
/**
* Normalizes a source value to a URL string.
* Source can be a plain string (e.g. "github:owner/repo") or an object
* with { name?, repo, subdir? } for installing from a subdirectory of a repo.
*/
export function getSourceUrl(source) {
if (typeof source === "string") return source
if (typeof source === "object" && source !== null && typeof source.repo === "string") {
return source.repo
}
throw new Error(`Invalid plugin source: ${JSON.stringify(source)}`)
}
/**
* Returns the subdir from an object source, or undefined for string sources.
*/
export function getSourceSubdir(source) {
if (typeof source === "object" && source !== null && typeof source.subdir === "string") {
return source.subdir
}
return undefined
}
/**
* Returns a display-friendly string for a source value.
*/
export function formatSource(source) {
if (typeof source === "string") return source
if (typeof source === "object" && source !== null) {
const parts = [source.repo]
if (source.subdir) parts.push(`(subdir: ${source.subdir})`)
return parts.join(" ")
}
return String(source)
}
export function isLocalSource(source) { export function isLocalSource(source) {
if (source.startsWith("./") || source.startsWith("../") || source.startsWith("/")) { const url = getSourceUrl(source)
if (url.startsWith("./") || url.startsWith("../") || url.startsWith("/")) {
return true return true
} }
// Windows absolute paths (e.g. C:\ or D:/) // Windows absolute paths (e.g. C:\ or D:/)
if (/^[A-Za-z]:[\\/]/.test(source)) { if (/^[A-Za-z]:[\\/]/.test(url)) {
return true return true
} }
return false return false
} }
export function extractPluginName(source) { export function extractPluginName(source) {
if (isLocalSource(source)) { if (typeof source === "object" && source !== null && typeof source.name === "string") {
return path.basename(source.replace(/[\/]+$/, "")) return source.name
} }
if (source.startsWith("github:")) { const url = getSourceUrl(source)
const withoutPrefix = source.replace("github:", "") if (isLocalSource(url)) {
return path.basename(url.replace(/[\/]+$/, ""))
}
if (url.startsWith("github:")) {
const withoutPrefix = url.replace("github:", "")
const [repoPath] = withoutPrefix.split("#") const [repoPath] = withoutPrefix.split("#")
const parts = repoPath.split("/") const parts = repoPath.split("/")
return parts[parts.length - 1] return parts[parts.length - 1]
} }
if (source.startsWith("git+") || source.startsWith("https://")) { if (url.startsWith("git+") || url.startsWith("https://")) {
const url = source.replace("git+", "") const cleaned = url.replace("git+", "")
const match = url.match(/\/([^/]+?)(?:\.git)?(?:#|$)/) const match = cleaned.match(/\/([^/]+?)(?:\.git)?(?:#|$)/)
return match?.[1] ?? source return match?.[1] ?? url
} }
return source return url
} }
export function readManifestFromPackageJson(pluginDir) { export function readManifestFromPackageJson(pluginDir) {
@ -123,28 +164,33 @@ export function readManifestFromPackageJson(pluginDir) {
} }
export function parseGitSource(source) { export function parseGitSource(source) {
if (isLocalSource(source)) { const url = getSourceUrl(source)
const resolved = path.resolve(source) const subdir = getSourceSubdir(source)
const name = path.basename(resolved) if (isLocalSource(url)) {
return { name, url: resolved, ref: undefined, local: true } const resolved = path.resolve(url)
const name = typeof source === "object" && source.name ? source.name : path.basename(resolved)
return { name, url: resolved, ref: undefined, local: true, subdir }
} }
if (source.startsWith("github:")) { if (url.startsWith("github:")) {
const [repoPath, ref] = source.replace("github:", "").split("#") const [repoPath, ref] = url.replace("github:", "").split("#")
const [owner, repo] = repoPath.split("/") const [owner, repo] = repoPath.split("/")
return { name: repo, url: `https://github.com/${owner}/${repo}.git`, ref } const name = typeof source === "object" && source.name ? source.name : repo
return { name, url: `https://github.com/${owner}/${repo}.git`, ref, subdir }
} }
if (source.startsWith("git+")) { if (url.startsWith("git+")) {
const raw = source.replace("git+", "") const raw = url.replace("git+", "")
const [url, ref] = raw.split("#") const [parsed, ref] = raw.split("#")
const name = path.basename(url, ".git") const name =
return { name, url, ref } typeof source === "object" && source.name ? source.name : path.basename(parsed, ".git")
return { name, url: parsed, ref, subdir }
} }
if (source.startsWith("https://")) { if (url.startsWith("https://")) {
const [url, ref] = source.split("#") const [parsed, ref] = url.split("#")
const name = path.basename(url, ".git") const name =
return { name, url, ref } typeof source === "object" && source.name ? source.name : path.basename(parsed, ".git")
return { name, url: parsed, ref, subdir }
} }
throw new Error(`Cannot parse plugin source: ${source}`) throw new Error(`Cannot parse plugin source: ${formatSource(source)}`)
} }
export function getGitCommit(pluginDir) { export function getGitCommit(pluginDir) {
@ -195,6 +241,8 @@ export function getEnrichedPlugins() {
name, name,
displayName: manifest?.displayName ?? name, displayName: manifest?.displayName ?? name,
source: entry.source, source: entry.source,
sourceDisplay: formatSource(entry.source),
subdir: getSourceSubdir(entry.source) ?? locked?.subdir ?? undefined,
enabled: entry.enabled ?? true, enabled: entry.enabled ?? true,
options: entry.options ?? {}, options: entry.options ?? {},
order: entry.order ?? 50, order: entry.order ?? 50,

View File

@ -15,12 +15,33 @@ import {
PLUGINS_DIR, PLUGINS_DIR,
LOCKFILE_PATH, LOCKFILE_PATH,
isLocalSource, isLocalSource,
getSourceUrl,
formatSource,
} from "./plugin-data.js" } from "./plugin-data.js"
const INTERNAL_EXPORTS = new Set(["manifest", "default"]) const INTERNAL_EXPORTS = new Set(["manifest", "default"])
const execAsync = promisify(execCb) const execAsync = promisify(execCb)
function cloneWithSubdir({ url, ref, subdir, pluginDir }) {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "quartz-plugin-"))
try {
if (ref) {
execSync(`git clone --depth 1 --branch ${ref} "${url}" "${tmpDir}"`, { stdio: "ignore" })
} else {
execSync(`git clone --depth 1 "${url}" "${tmpDir}"`, { stdio: "ignore" })
}
const subdirPath = path.join(tmpDir, subdir)
if (!fs.existsSync(subdirPath)) {
throw new Error(`Subdirectory "${subdir}" not found in cloned repository`)
}
fs.cpSync(subdirPath, pluginDir, { recursive: true })
return getGitCommit(tmpDir)
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true })
}
}
function buildPlugin(pluginDir, name) { function buildPlugin(pluginDir, name) {
try { try {
const skipBuild = !needsBuild(pluginDir) const skipBuild = !needsBuild(pluginDir)
@ -306,6 +327,18 @@ export async function handlePluginInstall() {
if (fs.existsSync(pluginDir)) { if (fs.existsSync(pluginDir)) {
try { try {
if (entry.subdir) {
if (!needsBuild(pluginDir)) {
console.log(
styleText(
"gray",
`${name}@${entry.commit.slice(0, 7)} already installed (subdir)`,
),
)
installed++
continue
}
} else {
const currentCommit = getGitCommit(pluginDir) const currentCommit = getGitCommit(pluginDir)
if (currentCommit === entry.commit && !needsBuild(pluginDir)) { if (currentCommit === entry.commit && !needsBuild(pluginDir)) {
console.log( console.log(
@ -315,11 +348,14 @@ export async function handlePluginInstall() {
continue continue
} }
if (currentCommit !== entry.commit) { if (currentCommit !== entry.commit) {
console.log(styleText("cyan", `${name}: updating to ${entry.commit.slice(0, 7)}...`)) console.log(
styleText("cyan", `${name}: updating to ${entry.commit.slice(0, 7)}...`),
)
const fetchRef = entry.ref ? ` ${entry.ref}` : "" const fetchRef = entry.ref ? ` ${entry.ref}` : ""
execSync(`git fetch --depth 1 origin${fetchRef}`, { cwd: pluginDir, stdio: "ignore" }) execSync(`git fetch --depth 1 origin${fetchRef}`, { cwd: pluginDir, stdio: "ignore" })
execSync(`git reset --hard ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" }) execSync(`git reset --hard ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" })
} }
}
pluginsToBuild.push({ name, pluginDir }) pluginsToBuild.push({ name, pluginDir })
installed++ installed++
} catch { } catch {
@ -328,6 +364,11 @@ export async function handlePluginInstall() {
} }
} else { } else {
try { try {
if (entry.subdir) {
console.log(styleText("cyan", `${name}: cloning (subdir: ${entry.subdir})...`))
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
cloneWithSubdir({ url: entry.resolved, ref: entry.ref, subdir: entry.subdir, pluginDir })
} else {
console.log(styleText("cyan", `${name}: cloning...`)) console.log(styleText("cyan", `${name}: cloning...`))
const branchArg = entry.ref ? ` --branch ${entry.ref}` : "" const branchArg = entry.ref ? ` --branch ${entry.ref}` : ""
execSync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`, { execSync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`, {
@ -340,6 +381,7 @@ export async function handlePluginInstall() {
}) })
execSync(`git checkout ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" }) execSync(`git checkout ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" })
} }
}
console.log(styleText("green", `${name}@${entry.commit.slice(0, 7)}`)) console.log(styleText("green", `${name}@${entry.commit.slice(0, 7)}`))
pluginsToBuild.push({ name, pluginDir }) pluginsToBuild.push({ name, pluginDir })
installed++ installed++
@ -391,7 +433,7 @@ export async function handlePluginAdd(sources) {
for (const source of sources) { for (const source of sources) {
try { try {
const { name, url, ref, local } = parseGitSource(source) const { name, url, ref, local, subdir } = parseGitSource(source)
const pluginDir = path.join(PLUGINS_DIR, name) const pluginDir = path.join(PLUGINS_DIR, name)
if (fs.existsSync(pluginDir)) { if (fs.existsSync(pluginDir)) {
@ -400,8 +442,8 @@ export async function handlePluginAdd(sources) {
} }
if (local) { if (local) {
// Local path: create symlink instead of git clone let resolvedPath = path.resolve(url)
const resolvedPath = path.resolve(url) if (subdir) resolvedPath = path.join(resolvedPath, subdir)
if (!fs.existsSync(resolvedPath)) { if (!fs.existsSync(resolvedPath)) {
console.log(styleText("red", `✗ Local path does not exist: ${resolvedPath}`)) console.log(styleText("red", `✗ Local path does not exist: ${resolvedPath}`))
continue continue
@ -413,10 +455,25 @@ export async function handlePluginAdd(sources) {
source, source,
resolved: resolvedPath, resolved: resolvedPath,
commit: "local", commit: "local",
...(subdir && { subdir }),
installedAt: new Date().toISOString(), installedAt: new Date().toISOString(),
} }
addedPlugins.push({ name, pluginDir, source }) addedPlugins.push({ name, pluginDir, source })
console.log(styleText("green", `✓ Added ${name} (local symlink)`)) console.log(styleText("green", `✓ Added ${name} (local symlink)`))
} else if (subdir) {
console.log(styleText("cyan", `→ Adding ${name} from ${url} (subdir: ${subdir})...`))
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
const commit = cloneWithSubdir({ url, ref, subdir, pluginDir })
lockfile.plugins[name] = {
source,
resolved: url,
commit,
...(ref && { ref }),
subdir,
installedAt: new Date().toISOString(),
}
addedPlugins.push({ name, pluginDir, source })
console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`))
} else { } else {
console.log(styleText("cyan", `→ Adding ${name} from ${url}...`)) console.log(styleText("cyan", `→ Adding ${name} from ${url}...`))
@ -441,7 +498,7 @@ export async function handlePluginAdd(sources) {
console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)}`)) console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)}`))
} }
} catch (error) { } catch (error) {
console.log(styleText("red", `✗ Failed to add ${source}: ${error}`)) console.log(styleText("red", `✗ Failed to add ${formatSource(source)}: ${error}`))
} }
} }
@ -525,7 +582,8 @@ export async function handlePluginRemove(names) {
if (pluginsJson?.plugins) { if (pluginsJson?.plugins) {
pluginsJson.plugins = pluginsJson.plugins.filter( pluginsJson.plugins = pluginsJson.plugins.filter(
(plugin) => (plugin) =>
!names.includes(extractPluginName(plugin.source)) && !names.includes(plugin.source), !names.includes(extractPluginName(plugin.source)) &&
!names.includes(formatSource(plugin.source)),
) )
writePluginsJson(pluginsJson) writePluginsJson(pluginsJson)
} }
@ -542,7 +600,7 @@ export async function handlePluginEnable(names) {
for (const name of names) { for (const name of names) {
const entry = json.plugins.find( const entry = json.plugins.find(
(e) => extractPluginName(e.source) === name || e.source === name, (e) => extractPluginName(e.source) === name || formatSource(e.source) === name,
) )
if (!entry) { if (!entry) {
console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`)) console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`))
@ -568,7 +626,7 @@ export async function handlePluginDisable(names) {
for (const name of names) { for (const name of names) {
const entry = json.plugins.find( const entry = json.plugins.find(
(e) => extractPluginName(e.source) === name || e.source === name, (e) => extractPluginName(e.source) === name || formatSource(e.source) === name,
) )
if (!entry) { if (!entry) {
console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`)) console.log(styleText("yellow", `⚠ Plugin "${name}" not found in quartz.config.yaml`))
@ -592,7 +650,9 @@ export async function handlePluginConfig(name, options = {}) {
return return
} }
const entry = json.plugins.find((e) => extractPluginName(e.source) === name || e.source === name) const entry = json.plugins.find(
(e) => extractPluginName(e.source) === name || formatSource(e.source) === name,
)
if (!entry) { if (!entry) {
console.log(styleText("red", `✗ Plugin "${name}" not found in quartz.config.yaml`)) console.log(styleText("red", `✗ Plugin "${name}" not found in quartz.config.yaml`))
return return
@ -617,7 +677,7 @@ export async function handlePluginConfig(name, options = {}) {
console.log(styleText("green", `✓ Set ${name}.${key} = ${JSON.stringify(value)}`)) console.log(styleText("green", `✓ Set ${name}.${key} = ${JSON.stringify(value)}`))
} else { } else {
console.log(styleText("bold", `Plugin: ${name}`)) console.log(styleText("bold", `Plugin: ${name}`))
console.log(` Source: ${entry.source}`) console.log(` Source: ${formatSource(entry.source)}`)
console.log(` Enabled: ${entry.enabled}`) console.log(` Enabled: ${entry.enabled}`)
console.log(` Order: ${entry.order ?? 50}`) console.log(` Order: ${entry.order ?? 50}`)
if (entry.options && Object.keys(entry.options).length > 0) { if (entry.options && Object.keys(entry.options).length > 0) {
@ -739,6 +799,30 @@ export async function handlePluginUpdate(names) {
try { try {
console.log(styleText("cyan", `→ Updating ${name}...`)) console.log(styleText("cyan", `→ Updating ${name}...`))
if (entry.subdir) {
fs.rmSync(pluginDir, { recursive: true })
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
const newCommit = cloneWithSubdir({
url: entry.resolved,
ref: entry.ref,
subdir: entry.subdir,
pluginDir,
})
if (newCommit !== entry.commit) {
entry.commit = newCommit
entry.installedAt = new Date().toISOString()
updatedPlugins.push({ name, pluginDir })
console.log(
styleText(
"green",
`✓ Updated ${name} to ${newCommit.slice(0, 7)} (subdir: ${entry.subdir})`,
),
)
} else {
console.log(styleText("gray", `${name} already up to date`))
}
} else {
const fetchRef = entry.ref || "" const fetchRef = entry.ref || ""
const resetTarget = entry.ref ? `origin/${entry.ref}` : "origin/HEAD" const resetTarget = entry.ref ? `origin/${entry.ref}` : "origin/HEAD"
execSync(`git fetch --depth 1 origin${fetchRef ? " " + fetchRef : ""}`, { execSync(`git fetch --depth 1 origin${fetchRef ? " " + fetchRef : ""}`, {
@ -756,6 +840,7 @@ export async function handlePluginUpdate(names) {
} else { } else {
console.log(styleText("gray", `${name} already up to date`)) console.log(styleText("gray", `${name} already up to date`))
} }
}
} catch (error) { } catch (error) {
console.log(styleText("red", `✗ Failed to update ${name}: ${error}`)) console.log(styleText("red", `✗ Failed to update ${name}: ${error}`))
} }
@ -797,7 +882,7 @@ export async function handlePluginList() {
const isLinked = exists && fs.lstatSync(pluginDir).isSymbolicLink() const isLinked = exists && fs.lstatSync(pluginDir).isSymbolicLink()
const status = isLinked ? styleText("green", "✓") : styleText("red", "✗") const status = isLinked ? styleText("green", "✓") : styleText("red", "✗")
console.log(` ${status} ${styleText("bold", name)}`) console.log(` ${status} ${styleText("bold", name)}`)
console.log(` Source: ${entry.source}`) console.log(` Source: ${formatSource(entry.source)}`)
console.log(` Type: local symlink`) console.log(` Type: local symlink`)
console.log(` Target: ${entry.resolved}`) console.log(` Target: ${entry.resolved}`)
console.log(` Installed: ${new Date(entry.installedAt).toLocaleDateString()}`) console.log(` Installed: ${new Date(entry.installedAt).toLocaleDateString()}`)
@ -818,7 +903,7 @@ export async function handlePluginList() {
: styleText("red", "✗") : styleText("red", "✗")
console.log(` ${status} ${styleText("bold", name)}`) console.log(` ${status} ${styleText("bold", name)}`)
console.log(` Source: ${entry.source}`) console.log(` Source: ${formatSource(entry.source)}`)
console.log(` Commit: ${entry.commit.slice(0, 7)}`) console.log(` Commit: ${entry.commit.slice(0, 7)}`)
if (currentCommit !== entry.commit && exists) { if (currentCommit !== entry.commit && exists) {
console.log(` Current: ${currentCommit.slice(0, 7)} (modified)`) console.log(` Current: ${currentCommit.slice(0, 7)} (modified)`)
@ -878,6 +963,16 @@ export async function handlePluginRestore() {
} }
try { try {
if (entry.subdir) {
console.log(
styleText(
"cyan",
`${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)} (subdir: ${entry.subdir})...`,
),
)
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
cloneWithSubdir({ url: entry.resolved, ref: entry.ref, subdir: entry.subdir, pluginDir })
} else {
console.log( console.log(
styleText("cyan", `${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)}...`), styleText("cyan", `${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)}...`),
) )
@ -886,6 +981,7 @@ export async function handlePluginRestore() {
stdio: "ignore", stdio: "ignore",
}) })
execSync(`git checkout ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" }) execSync(`git checkout ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" })
}
console.log(styleText("green", `${name} restored`)) console.log(styleText("green", `${name} restored`))
restoredPlugins.push({ name, pluginDir }) restoredPlugins.push({ name, pluginDir })
installed++ installed++
@ -997,8 +1093,7 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
const name = extractPluginName(entry.source) const name = extractPluginName(entry.source)
const pluginDir = path.join(PLUGINS_DIR, name) const pluginDir = path.join(PLUGINS_DIR, name)
if (lockfile.plugins[name] && fs.existsSync(pluginDir)) return false if (lockfile.plugins[name] && fs.existsSync(pluginDir)) return false
// Only attempt sources that parseGitSource can handle (git URLs + local paths) const src = getSourceUrl(entry.source)
const src = entry.source
return ( return (
src.startsWith("github:") || src.startsWith("github:") ||
src.startsWith("git+") || src.startsWith("git+") ||
@ -1015,7 +1110,7 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
console.log(`Found ${missing.length} uninstalled plugin(s) in config:\n`) console.log(`Found ${missing.length} uninstalled plugin(s) in config:\n`)
for (const entry of missing) { for (const entry of missing) {
const name = extractPluginName(entry.source) const name = extractPluginName(entry.source)
console.log(` ${styleText("yellow", name)}${entry.source}`) console.log(` ${styleText("yellow", name)}${formatSource(entry.source)}`)
} }
console.log() console.log()
@ -1031,7 +1126,7 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
for (const entry of missing) { for (const entry of missing) {
try { try {
const { name, url, ref, local } = parseGitSource(entry.source) const { name, url, ref, local, subdir } = parseGitSource(entry.source)
const pluginDir = path.join(PLUGINS_DIR, name) const pluginDir = path.join(PLUGINS_DIR, name)
if (fs.existsSync(pluginDir)) { if (fs.existsSync(pluginDir)) {
@ -1041,6 +1136,7 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
source: entry.source, source: entry.source,
resolved: url, resolved: url,
commit: "local", commit: "local",
...(subdir && { subdir }),
installedAt: new Date().toISOString(), installedAt: new Date().toISOString(),
} }
installed.push({ name, pluginDir }) installed.push({ name, pluginDir })
@ -1053,6 +1149,7 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
resolved: url, resolved: url,
commit, commit,
...(ref && { ref }), ...(ref && { ref }),
...(subdir && { subdir }),
installedAt: new Date().toISOString(), installedAt: new Date().toISOString(),
} }
installed.push({ name, pluginDir }) installed.push({ name, pluginDir })
@ -1061,7 +1158,8 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
if (local) { if (local) {
// Local path: symlink // Local path: symlink
const resolvedPath = path.resolve(url) let resolvedPath = path.resolve(url)
if (subdir) resolvedPath = path.join(resolvedPath, subdir)
if (!fs.existsSync(resolvedPath)) { if (!fs.existsSync(resolvedPath)) {
console.log(styleText("red", `✗ Local path does not exist: ${resolvedPath}`)) console.log(styleText("red", `✗ Local path does not exist: ${resolvedPath}`))
failed++ failed++
@ -1074,10 +1172,27 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
source: entry.source, source: entry.source,
resolved: resolvedPath, resolved: resolvedPath,
commit: "local", commit: "local",
...(subdir && { subdir }),
installedAt: new Date().toISOString(), installedAt: new Date().toISOString(),
} }
installed.push({ name, pluginDir }) installed.push({ name, pluginDir })
console.log(styleText("green", `✓ Linked ${name} (local)`)) console.log(styleText("green", `✓ Linked ${name} (local)`))
} else if (subdir) {
console.log(styleText("cyan", `→ Cloning ${name} from ${url} (subdir: ${subdir})...`))
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
const commit = cloneWithSubdir({ url, ref, subdir, pluginDir })
lockfile.plugins[name] = {
source: entry.source,
resolved: url,
commit,
...(ref && { ref }),
subdir,
installedAt: new Date().toISOString(),
}
installed.push({ name, pluginDir })
console.log(
styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`),
)
} else { } else {
console.log(styleText("cyan", `→ Cloning ${name} from ${url}...`)) console.log(styleText("cyan", `→ Cloning ${name} from ${url}...`))
@ -1102,7 +1217,7 @@ export async function handlePluginResolve({ dryRun = false } = {}) {
console.log(styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)}`)) console.log(styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)}`))
} }
} catch (error) { } catch (error) {
console.log(styleText("red", `✗ Failed to resolve ${entry.source}: ${error}`)) console.log(styleText("red", `✗ Failed to resolve ${formatSource(entry.source)}: ${error}`))
failed++ failed++
} }
} }

View File

@ -246,7 +246,10 @@ plugins:
position: beforeBody position: beforeBody
priority: 15 priority: 15
display: all display: all
- source: github:saberzero1/quartz-themes - source:
name: quartz-themes
repo: github:saberzero1/quartz-themes
subdir: plugin
enabled: true enabled: true
options: options:
theme: default theme: default

View File

@ -249,7 +249,10 @@ plugins:
# TTRPG-specific plugins # TTRPG-specific plugins
- source: github:Requiae/quartz-leaflet-map-plugin - source: github:Requiae/quartz-leaflet-map-plugin
enabled: true enabled: true
- source: github:saberzero1/quartz-themes - source:
name: quartz-themes
repo: github:saberzero1/quartz-themes
subdir: plugin
enabled: true enabled: true
options: options:
theme: its-theme theme: its-theme