feat: parallel git command handling

This commit is contained in:
saberzero1
2026-04-03 14:39:30 +02:00
parent 52e4257bda
commit 2a6117142c

View File

@@ -44,6 +44,26 @@ function cloneWithSubdir({ url, ref, subdir, pluginDir }) {
} }
} }
async function cloneWithSubdirAsync({ url, ref, subdir, pluginDir }) {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "quartz-plugin-"))
try {
if (ref) {
await execAsync(`git clone --depth 1 --branch ${ref} "${url}" "${tmpDir}"`)
} else {
await execAsync(`git clone --depth 1 "${url}" "${tmpDir}"`)
}
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 })
const { stdout } = await execAsync("git rev-parse HEAD", { cwd: tmpDir })
return stdout.trim()
} 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)
@@ -492,6 +512,8 @@ export async function handlePluginInstallUnified({
let failed = 0 let failed = 0
let lockfileChanged = false let lockfileChanged = false
// Handle existing dirs and local symlinks (fast), collect remote clones
const remoteEntries = []
for (const entry of missing) { for (const entry of missing) {
try { try {
const { name, url, ref, local, subdir } = parseGitSource(entry.source) const { name, url, ref, local, subdir } = parseGitSource(entry.source)
@@ -549,46 +571,8 @@ export async function handlePluginInstallUnified({
installed.push({ name, pluginDir }) installed.push({ name, pluginDir })
lockfileChanged = true lockfileChanged = true
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 })
lockfileChanged = true
console.log(
styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`),
)
} else { } else {
console.log(styleText("cyan", `→ Cloning ${name} from ${url}...`)) remoteEntries.push({ entry, name, url, ref, subdir, pluginDir })
if (ref) {
execSync(`git clone --depth 1 --branch ${ref} "${url}" "${pluginDir}"`, {
stdio: "ignore",
})
} else {
execSync(`git clone --depth 1 "${url}" "${pluginDir}"`, { stdio: "ignore" })
}
const commit = getGitCommit(pluginDir)
lockfile.plugins[name] = {
source: entry.source,
resolved: url,
commit,
...(ref && { ref }),
installedAt: new Date().toISOString(),
}
installed.push({ name, pluginDir })
lockfileChanged = true
console.log(styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)}`))
} }
} catch (error) { } catch (error) {
console.log(styleText("red", `✗ Failed to resolve ${formatSource(entry.source)}: ${error}`)) console.log(styleText("red", `✗ Failed to resolve ${formatSource(entry.source)}: ${error}`))
@@ -596,6 +580,61 @@ export async function handlePluginInstallUnified({
} }
} }
// Clone remote plugins in parallel
if (remoteEntries.length > 0) {
const concurrency = Math.max(1, os.cpus().length)
await runParallel(
remoteEntries,
concurrency,
async ({ entry, name, url, ref, subdir, pluginDir }) => {
try {
if (subdir) {
console.log(styleText("cyan", `→ Cloning ${name} from ${url} (subdir: ${subdir})...`))
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
const commit = await cloneWithSubdirAsync({ url, ref, subdir, pluginDir })
lockfile.plugins[name] = {
source: entry.source,
resolved: url,
commit,
...(ref && { ref }),
subdir,
installedAt: new Date().toISOString(),
}
installed.push({ name, pluginDir })
lockfileChanged = true
console.log(
styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`),
)
} else {
console.log(styleText("cyan", `→ Cloning ${name} from ${url}...`))
const branchArg = ref ? ` --branch ${ref}` : ""
await execAsync(`git clone --depth 1${branchArg} "${url}" "${pluginDir}"`)
const { stdout } = await execAsync("git rev-parse HEAD", { cwd: pluginDir })
const commit = stdout.trim()
lockfile.plugins[name] = {
source: entry.source,
resolved: url,
commit,
...(ref && { ref }),
installedAt: new Date().toISOString(),
}
installed.push({ name, pluginDir })
lockfileChanged = true
console.log(styleText("green", `✓ Cloned ${name}@${commit.slice(0, 7)}`))
}
} catch (error) {
console.log(
styleText("red", `✗ Failed to resolve ${formatSource(entry.source)}: ${error}`),
)
failed++
}
},
)
}
if (installed.length > 0) { if (installed.length > 0) {
console.log() console.log()
console.log(styleText("cyan", "→ Building plugins...")) console.log(styleText("cyan", "→ Building plugins..."))
@@ -692,6 +731,8 @@ export async function handlePluginInstallUnified({
nameFilter ? nameFilter.has(name) : true, nameFilter ? nameFilter.has(name) : true,
) )
// Handle local symlinks and collect remote plugins to clone
const remotePlugins = []
for (const [name, entry] of entries) { for (const [name, entry] of entries) {
const pluginDir = path.join(PLUGINS_DIR, name) const pluginDir = path.join(PLUGINS_DIR, name)
@@ -719,36 +760,47 @@ export async function handlePluginInstallUnified({
continue continue
} }
try { remotePlugins.push({ name, entry, pluginDir })
if (entry.subdir) { }
console.log(
styleText( // Clone remote plugins in parallel
"cyan", if (remotePlugins.length > 0) {
`${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)} (subdir: ${entry.subdir})...`, const concurrency = Math.max(1, os.cpus().length)
), await runParallel(remotePlugins, concurrency, async ({ name, entry, pluginDir }) => {
) try {
fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) if (entry.subdir) {
cloneWithSubdir({ url: entry.resolved, ref: entry.ref, subdir: entry.subdir, pluginDir }) console.log(
} else { styleText(
console.log( "cyan",
styleText( `${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)} (subdir: ${entry.subdir})...`,
"cyan", ),
`${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)}...`, )
), fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
) await cloneWithSubdirAsync({
const branchArg = entry.ref ? ` --branch ${entry.ref}` : "" url: entry.resolved,
execSync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`, { ref: entry.ref,
stdio: "ignore", subdir: entry.subdir,
}) pluginDir,
execSync(`git checkout ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" }) })
} else {
console.log(
styleText(
"cyan",
`${name}: cloning ${entry.resolved}@${entry.commit.slice(0, 7)}...`,
),
)
const branchArg = entry.ref ? ` --branch ${entry.ref}` : ""
await execAsync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`)
await execAsync(`git checkout ${entry.commit}`, { cwd: pluginDir })
}
console.log(styleText("green", `${name} restored`))
restoredPlugins.push({ name, pluginDir })
installed++
} catch {
console.log(styleText("red", `${name}: failed to restore`))
failed++
} }
console.log(styleText("green", `${name} restored`)) })
restoredPlugins.push({ name, pluginDir })
installed++
} catch {
console.log(styleText("red", `${name}: failed to restore`))
failed++
}
} }
if (restoredPlugins.length > 0) { if (restoredPlugins.length > 0) {
@@ -787,6 +839,8 @@ export async function handlePluginInstallUnified({
const updatedPlugins = [] const updatedPlugins = []
let lockfileChanged = false let lockfileChanged = false
// Phase 1: Validate and categorize plugins (fast, sequential)
const validPlugins = []
for (const name of pluginsToUpdate) { for (const name of pluginsToUpdate) {
const entry = lockfile.plugins[name] const entry = lockfile.plugins[name]
if (!entry) { if (!entry) {
@@ -808,59 +862,68 @@ export async function handlePluginInstallUnified({
continue continue
} }
try { validPlugins.push({ name, pluginDir, entry })
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 (needsBuild(pluginDir)) {
updatedPlugins.push({ name, pluginDir })
}
if (newCommit !== entry.commit) {
entry.commit = newCommit
entry.installedAt = new Date().toISOString()
lockfileChanged = true
console.log(
styleText(
"green",
`✓ Updated ${name} to ${newCommit.slice(0, 7)} (subdir: ${entry.subdir})`,
),
)
} else {
console.log(styleText("gray", `${name} rebuilt (subdir: ${entry.subdir})`))
}
} else {
const fetchRef = entry.ref || ""
const resetTarget = entry.ref ? `origin/${entry.ref}` : "origin/HEAD"
execSync(`git fetch --depth 1 origin${fetchRef ? " " + fetchRef : ""}`, {
cwd: pluginDir,
stdio: "ignore",
})
execSync(`git reset --hard ${resetTarget}`, { cwd: pluginDir, stdio: "ignore" })
const newCommit = getGitCommit(pluginDir)
if (newCommit !== entry.commit) {
entry.commit = newCommit
entry.installedAt = new Date().toISOString()
updatedPlugins.push({ name, pluginDir })
lockfileChanged = true
console.log(styleText("green", `✓ Updated ${name} to ${newCommit.slice(0, 7)}`))
} else {
console.log(styleText("gray", `${name} already up to date`))
}
}
} catch (error) {
console.log(styleText("red", `✗ Failed to update ${name}: ${error}`))
}
} }
// Phase 2: Fetch/update plugins in parallel
if (validPlugins.length > 0) {
const concurrency = Math.max(1, os.cpus().length)
await runParallel(validPlugins, concurrency, async ({ name, pluginDir, entry }) => {
try {
console.log(styleText("cyan", `→ Updating ${name}...`))
if (entry.subdir) {
fs.rmSync(pluginDir, { recursive: true })
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
const newCommit = await cloneWithSubdirAsync({
url: entry.resolved,
ref: entry.ref,
subdir: entry.subdir,
pluginDir,
})
if (needsBuild(pluginDir)) {
updatedPlugins.push({ name, pluginDir })
}
if (newCommit !== entry.commit) {
entry.commit = newCommit
entry.installedAt = new Date().toISOString()
lockfileChanged = true
console.log(
styleText(
"green",
`✓ Updated ${name} to ${newCommit.slice(0, 7)} (subdir: ${entry.subdir})`,
),
)
} else {
console.log(styleText("gray", `${name} rebuilt (subdir: ${entry.subdir})`))
}
} else {
const fetchRef = entry.ref || ""
const resetTarget = entry.ref ? `origin/${entry.ref}` : "origin/HEAD"
await execAsync(`git fetch --depth 1 origin${fetchRef ? " " + fetchRef : ""}`, {
cwd: pluginDir,
})
await execAsync(`git reset --hard ${resetTarget}`, { cwd: pluginDir })
const { stdout } = await execAsync("git rev-parse HEAD", { cwd: pluginDir })
const newCommit = stdout.trim()
if (newCommit !== entry.commit) {
entry.commit = newCommit
entry.installedAt = new Date().toISOString()
updatedPlugins.push({ name, pluginDir })
lockfileChanged = true
console.log(styleText("green", `✓ Updated ${name} to ${newCommit.slice(0, 7)}`))
} else {
console.log(styleText("gray", `${name} already up to date`))
}
}
} catch (error) {
console.log(styleText("red", `✗ Failed to update ${name}: ${error}`))
}
})
}
// Phase 3: Build updated plugins in parallel
if (updatedPlugins.length > 0) { if (updatedPlugins.length > 0) {
console.log() console.log()
console.log(styleText("cyan", "→ Rebuilding updated plugins...")) console.log(styleText("cyan", "→ Rebuilding updated plugins..."))
@@ -898,6 +961,8 @@ export async function handlePluginInstallUnified({
let failed = 0 let failed = 0
const pluginsToBuild = [] const pluginsToBuild = []
// Handle local plugins and collect entries needing git operations
const gitEntries = []
for (const [name, entry] of entries) { for (const [name, entry] of entries) {
const pluginDir = path.join(PLUGINS_DIR, name) const pluginDir = path.join(PLUGINS_DIR, name)
@@ -931,70 +996,79 @@ export async function handlePluginInstallUnified({
} }
if (fs.existsSync(pluginDir)) { if (fs.existsSync(pluginDir)) {
try { if (entry.subdir) {
if (entry.subdir) { if (!needsBuild(pluginDir)) {
if (!needsBuild(pluginDir)) { console.log(
console.log( styleText("gray", `${name}@${entry.commit.slice(0, 7)} already installed (subdir)`),
styleText( )
"gray", installed++
`${name}@${entry.commit.slice(0, 7)} already installed (subdir)`, continue
),
)
installed++
continue
}
} else {
const currentCommit = getGitCommit(pluginDir)
if (currentCommit === entry.commit && !needsBuild(pluginDir)) {
console.log(
styleText("gray", `${name}@${entry.commit.slice(0, 7)} already installed`),
)
installed++
continue
}
if (currentCommit !== entry.commit) {
console.log(
styleText("cyan", `${name}: updating to ${entry.commit.slice(0, 7)}...`),
)
const fetchRef = entry.ref ? ` ${entry.ref}` : ""
execSync(`git fetch --depth 1 origin${fetchRef}`, { 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 { } else {
console.log(styleText("red", `${name}: failed to update`)) const currentCommit = getGitCommit(pluginDir)
failed++ if (currentCommit === entry.commit && !needsBuild(pluginDir)) {
console.log(
styleText("gray", `${name}@${entry.commit.slice(0, 7)} already installed`),
)
installed++
continue
}
if (currentCommit !== entry.commit) {
gitEntries.push({ name, entry, pluginDir, action: "update" })
} else {
pluginsToBuild.push({ name, pluginDir })
installed++
}
} }
} else { } else {
gitEntries.push({ name, entry, pluginDir, action: "clone" })
}
}
// Run git fetch/clone operations in parallel
if (gitEntries.length > 0) {
const concurrency = Math.max(1, os.cpus().length)
await runParallel(gitEntries, concurrency, async ({ name, entry, pluginDir, action }) => {
try { try {
if (entry.subdir) { if (action === "update") {
console.log(styleText("cyan", `${name}: cloning (subdir: ${entry.subdir})...`)) console.log(styleText("cyan", `${name}: updating to ${entry.commit.slice(0, 7)}...`))
fs.mkdirSync(path.dirname(pluginDir), { recursive: true }) const fetchRef = entry.ref ? ` ${entry.ref}` : ""
cloneWithSubdir({ url: entry.resolved, ref: entry.ref, subdir: entry.subdir, pluginDir }) await execAsync(`git fetch --depth 1 origin${fetchRef}`, { cwd: pluginDir })
await execAsync(`git reset --hard ${entry.commit}`, { cwd: pluginDir })
pluginsToBuild.push({ name, pluginDir })
installed++
} else { } else {
console.log(styleText("cyan", `${name}: cloning...`)) if (entry.subdir) {
const branchArg = entry.ref ? ` --branch ${entry.ref}` : "" console.log(styleText("cyan", `${name}: cloning (subdir: ${entry.subdir})...`))
execSync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`, { fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
stdio: "ignore", await cloneWithSubdirAsync({
}) url: entry.resolved,
if (entry.commit !== "unknown") { ref: entry.ref,
execSync(`git fetch --depth 1 origin ${entry.commit}`, { subdir: entry.subdir,
cwd: pluginDir, pluginDir,
stdio: "ignore",
}) })
execSync(`git checkout ${entry.commit}`, { cwd: pluginDir, stdio: "ignore" }) } else {
console.log(styleText("cyan", `${name}: cloning...`))
const branchArg = entry.ref ? ` --branch ${entry.ref}` : ""
await execAsync(`git clone --depth 1${branchArg} "${entry.resolved}" "${pluginDir}"`)
if (entry.commit !== "unknown") {
await execAsync(`git fetch --depth 1 origin ${entry.commit}`, { cwd: pluginDir })
await execAsync(`git checkout ${entry.commit}`, { cwd: pluginDir })
}
} }
console.log(styleText("green", `${name}@${entry.commit.slice(0, 7)}`))
pluginsToBuild.push({ name, pluginDir })
installed++
} }
console.log(styleText("green", `${name}@${entry.commit.slice(0, 7)}`))
pluginsToBuild.push({ name, pluginDir })
installed++
} catch { } catch {
console.log(styleText("red", `${name}: failed to clone`)) console.log(
styleText("red", `${name}: failed to ${action === "update" ? "update" : "clone"}`),
)
failed++ failed++
} }
} })
} }
if (pluginsToBuild.length > 0) { if (pluginsToBuild.length > 0) {
@@ -1052,6 +1126,8 @@ export async function handlePluginAdd(
const addedPlugins = [] const addedPlugins = []
// Handle local plugins and collect remote sources to clone
const remoteSources = []
for (const source of sources) { for (const source of sources) {
try { try {
const parsed = parseGitSource(source) const parsed = parseGitSource(source)
@@ -1093,48 +1169,64 @@ export async function handlePluginAdd(
} }
addedPlugins.push({ name, pluginDir, source, configSource }) addedPlugins.push({ name, pluginDir, source, configSource })
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, configSource })
console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`))
} else { } else {
console.log(styleText("cyan", `→ Adding ${name} from ${url}...`)) remoteSources.push({ source, name, url, ref, subdir, pluginDir, configSource })
if (ref) {
execSync(`git clone --depth 1 --branch ${ref} "${url}" "${pluginDir}"`, {
stdio: "ignore",
})
} else {
execSync(`git clone --depth 1 "${url}" "${pluginDir}"`, { stdio: "ignore" })
}
const commit = getGitCommit(pluginDir)
lockfile.plugins[name] = {
source,
resolved: url,
commit,
...(ref && { ref }),
installedAt: new Date().toISOString(),
}
addedPlugins.push({ name, pluginDir, source, configSource })
console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)}`))
} }
} catch (error) { } catch (error) {
console.log(styleText("red", `✗ Failed to add ${formatSource(source)}: ${error}`)) console.log(styleText("red", `✗ Failed to add ${formatSource(source)}: ${error}`))
} }
} }
// Clone remote plugins in parallel
if (remoteSources.length > 0) {
const concurrency = Math.max(1, os.cpus().length)
await runParallel(
remoteSources,
concurrency,
async ({ source, name, url, ref, subdir, pluginDir, configSource }) => {
try {
if (subdir) {
console.log(styleText("cyan", `→ Adding ${name} from ${url} (subdir: ${subdir})...`))
fs.mkdirSync(path.dirname(pluginDir), { recursive: true })
const commit = await cloneWithSubdirAsync({ url, ref, subdir, pluginDir })
lockfile.plugins[name] = {
source,
resolved: url,
commit,
...(ref && { ref }),
subdir,
installedAt: new Date().toISOString(),
}
addedPlugins.push({ name, pluginDir, source, configSource })
console.log(
styleText("green", `✓ Added ${name}@${commit.slice(0, 7)} (subdir: ${subdir})`),
)
} else {
console.log(styleText("cyan", `→ Adding ${name} from ${url}...`))
const branchArg = ref ? ` --branch ${ref}` : ""
await execAsync(`git clone --depth 1${branchArg} "${url}" "${pluginDir}"`)
const { stdout } = await execAsync("git rev-parse HEAD", { cwd: pluginDir })
const commit = stdout.trim()
lockfile.plugins[name] = {
source,
resolved: url,
commit,
...(ref && { ref }),
installedAt: new Date().toISOString(),
}
addedPlugins.push({ name, pluginDir, source, configSource })
console.log(styleText("green", `✓ Added ${name}@${commit.slice(0, 7)}`))
}
} catch (error) {
console.log(styleText("red", `✗ Failed to add ${formatSource(source)}: ${error}`))
}
},
)
}
if (addedPlugins.length > 0) { if (addedPlugins.length > 0) {
console.log() console.log()
console.log(styleText("cyan", "→ Building plugins...")) console.log(styleText("cyan", "→ Building plugins..."))