refactor: extract shared resolveCommandPath helper for colon-splitting
Deduplicate colon-separated command name logic across all 4 targets (opencode, droid, gemini, qwen) into a single resolveCommandPath() helper in utils/files.ts. Addresses review feedback on PR #251. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { copyDir, ensureDir, writeText } from "../utils/files"
|
||||
import { copyDir, ensureDir, resolveCommandPath, writeText } from "../utils/files"
|
||||
import type { DroidBundle } from "../types/droid"
|
||||
|
||||
export async function writeDroidBundle(outputRoot: string, bundle: DroidBundle): Promise<void> {
|
||||
@@ -9,16 +9,8 @@ export async function writeDroidBundle(outputRoot: string, bundle: DroidBundle):
|
||||
if (bundle.commands.length > 0) {
|
||||
await ensureDir(paths.commandsDir)
|
||||
for (const command of bundle.commands) {
|
||||
// Split colon-separated names into nested directories (e.g. "ce:plan" -> "ce/plan.md")
|
||||
// to avoid colons in filenames which are invalid on Windows/NTFS
|
||||
const parts = command.name.split(":")
|
||||
if (parts.length > 1) {
|
||||
const nestedDir = path.join(paths.commandsDir, ...parts.slice(0, -1))
|
||||
await ensureDir(nestedDir)
|
||||
await writeText(path.join(nestedDir, `${parts[parts.length - 1]}.md`), command.content + "\n")
|
||||
} else {
|
||||
await writeText(path.join(paths.commandsDir, `${command.name}.md`), command.content + "\n")
|
||||
}
|
||||
const dest = await resolveCommandPath(paths.commandsDir, command.name, ".md")
|
||||
await writeText(dest, command.content + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { backupFile, copyDir, ensureDir, pathExists, readJson, writeJson, writeText } from "../utils/files"
|
||||
import { backupFile, copyDir, ensureDir, pathExists, readJson, resolveCommandPath, writeJson, writeText } from "../utils/files"
|
||||
import type { GeminiBundle } from "../types/gemini"
|
||||
|
||||
export async function writeGeminiBundle(outputRoot: string, bundle: GeminiBundle): Promise<void> {
|
||||
@@ -20,16 +20,8 @@ export async function writeGeminiBundle(outputRoot: string, bundle: GeminiBundle
|
||||
|
||||
if (bundle.commands.length > 0) {
|
||||
for (const command of bundle.commands) {
|
||||
// Split colon-separated names into nested directories (e.g. "ce:plan" -> "ce/plan.toml")
|
||||
// to avoid colons in filenames which are invalid on Windows/NTFS
|
||||
const parts = command.name.split(":")
|
||||
if (parts.length > 1) {
|
||||
const nestedDir = path.join(paths.commandsDir, ...parts.slice(0, -1))
|
||||
await ensureDir(nestedDir)
|
||||
await writeText(path.join(nestedDir, `${parts[parts.length - 1]}.toml`), command.content + "\n")
|
||||
} else {
|
||||
await writeText(path.join(paths.commandsDir, `${command.name}.toml`), command.content + "\n")
|
||||
}
|
||||
const dest = await resolveCommandPath(paths.commandsDir, command.name, ".toml")
|
||||
await writeText(dest, command.content + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { backupFile, copyDir, ensureDir, pathExists, readJson, writeJson, writeText } from "../utils/files"
|
||||
import { backupFile, copyDir, ensureDir, pathExists, readJson, resolveCommandPath, writeJson, writeText } from "../utils/files"
|
||||
import type { OpenCodeBundle, OpenCodeConfig } from "../types/opencode"
|
||||
|
||||
// Merges plugin config into existing opencode.json. User keys win on conflict. See ADR-002.
|
||||
@@ -75,17 +75,7 @@ export async function writeOpenCodeBundle(outputRoot: string, bundle: OpenCodeBu
|
||||
}
|
||||
|
||||
for (const commandFile of bundle.commandFiles) {
|
||||
// Split colon-separated names into nested directories (e.g. "ce:plan" -> "ce/plan.md")
|
||||
// to avoid colons in filenames which are invalid on Windows/NTFS
|
||||
const parts = commandFile.name.split(":")
|
||||
let dest: string
|
||||
if (parts.length > 1) {
|
||||
const nestedDir = path.join(openCodePaths.commandDir, ...parts.slice(0, -1))
|
||||
await ensureDir(nestedDir)
|
||||
dest = path.join(nestedDir, `${parts[parts.length - 1]}.md`)
|
||||
} else {
|
||||
dest = path.join(openCodePaths.commandDir, `${commandFile.name}.md`)
|
||||
}
|
||||
const dest = await resolveCommandPath(openCodePaths.commandDir, commandFile.name, ".md")
|
||||
const cmdBackupPath = await backupFile(dest)
|
||||
if (cmdBackupPath) {
|
||||
console.log(`Backed up existing command file to ${cmdBackupPath}`)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import { backupFile, copyDir, ensureDir, writeJson, writeText } from "../utils/files"
|
||||
import { backupFile, copyDir, ensureDir, resolveCommandPath, writeJson, writeText } from "../utils/files"
|
||||
import type { QwenBundle, QwenExtensionConfig } from "../types/qwen"
|
||||
|
||||
export async function writeQwenBundle(outputRoot: string, bundle: QwenBundle): Promise<void> {
|
||||
@@ -31,15 +31,8 @@ export async function writeQwenBundle(outputRoot: string, bundle: QwenBundle): P
|
||||
const commandsDir = qwenPaths.commandsDir
|
||||
await ensureDir(commandsDir)
|
||||
for (const commandFile of bundle.commandFiles) {
|
||||
// Support nested commands with colon separator
|
||||
const parts = commandFile.name.split(":")
|
||||
if (parts.length > 1) {
|
||||
const nestedDir = path.join(commandsDir, ...parts.slice(0, -1))
|
||||
await ensureDir(nestedDir)
|
||||
await writeText(path.join(nestedDir, `${parts[parts.length - 1]}.md`), commandFile.content + "\n")
|
||||
} else {
|
||||
await writeText(path.join(commandsDir, `${commandFile.name}.md`), commandFile.content + "\n")
|
||||
}
|
||||
const dest = await resolveCommandPath(commandsDir, commandFile.name, ".md")
|
||||
await writeText(dest, commandFile.content + "\n")
|
||||
}
|
||||
|
||||
// Copy skills
|
||||
|
||||
@@ -75,6 +75,21 @@ export async function walkFiles(root: string): Promise<string[]> {
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a colon-separated command name into a filesystem path.
|
||||
* e.g. resolveCommandPath("/commands", "ce:plan", ".md") -> "/commands/ce/plan.md"
|
||||
* Creates intermediate directories as needed.
|
||||
*/
|
||||
export async function resolveCommandPath(dir: string, name: string, ext: string): Promise<string> {
|
||||
const parts = name.split(":")
|
||||
if (parts.length > 1) {
|
||||
const nestedDir = path.join(dir, ...parts.slice(0, -1))
|
||||
await ensureDir(nestedDir)
|
||||
return path.join(nestedDir, `${parts[parts.length - 1]}${ext}`)
|
||||
}
|
||||
return path.join(dir, `${name}${ext}`)
|
||||
}
|
||||
|
||||
export async function copyDir(sourceDir: string, targetDir: string): Promise<void> {
|
||||
await ensureDir(targetDir)
|
||||
const entries = await fs.readdir(sourceDir, { withFileTypes: true })
|
||||
|
||||
Reference in New Issue
Block a user