Add optional update: command support to collections YAML

Collections can now specify an optional 'update:' key with a bash command
that will be executed during 'qmd update' before indexing files.

Features:
- Runs command in collection directory (cwd)
- Uses /usr/bin/env bash -c "..."
- Always prints stdout/stderr output
- Stops with exit code if command fails (!= 0)
- Legacy --pull flag still works for git repos without update command

Example YAML:
```yaml
collections:
  archive:
    path: /path/to/repo
    pattern: "**/*.md"
    update: git pull
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Tobi Lutke 2025-12-13 11:16:24 -05:00
parent 49207c524e
commit b4e60a08e5
No known key found for this signature in database
2 changed files with 52 additions and 8 deletions

View File

@ -28,6 +28,7 @@ export interface Collection {
path: string; // Absolute path to index
pattern: string; // Glob pattern (e.g., "**/*.md")
context?: ContextMap; // Optional context definitions
update?: string; // Optional bash command to run during qmd update
}
/**
@ -49,8 +50,17 @@ export interface NamedCollection extends Collection {
// Configuration paths
// ============================================================================
const CONFIG_DIR = join(homedir(), ".config", "qmd");
const CONFIG_PATH = join(CONFIG_DIR, "index.yml");
function getConfigDir(): string {
// Allow override via QMD_CONFIG_DIR for testing
if (process.env.QMD_CONFIG_DIR) {
return process.env.QMD_CONFIG_DIR;
}
return join(homedir(), ".config", "qmd");
}
function getConfigFilePath(): string {
return join(getConfigDir(), "index.yml");
}
/**
* Ensure config directory exists

View File

@ -83,6 +83,7 @@ import {
escapeCSV,
type OutputFormat,
} from "./formatter.js";
import { getCollection as getCollectionFromYaml } from "./collections.js";
// Chunking: ~2000 tokens per chunk, ~3 bytes/token = 6KB
const CHUNK_BYTE_SIZE = 6 * 1024;
@ -528,10 +529,10 @@ async function updateCollections(pullFirst: boolean = false): Promise<void> {
// Clear Ollama cache on update
clearCache(db);
const collections = db.prepare(`SELECT id, pwd, glob_pattern FROM collections`).all() as { id: number; pwd: string; glob_pattern: string }[];
const collections = listCollections(db);
if (collections.length === 0) {
console.log(`${c.dim}No collections found. Run 'qmd add .' to index markdown files.${c.reset}`);
console.log(`${c.dim}No collections found. Run 'qmd collection add .' to index markdown files.${c.reset}`);
closeDb();
return;
}
@ -541,10 +542,43 @@ async function updateCollections(pullFirst: boolean = false): Promise<void> {
for (let i = 0; i < collections.length; i++) {
const col = collections[i];
console.log(`${c.cyan}[${i + 1}/${collections.length}]${c.reset} ${c.bold}${col.pwd}${c.reset}`);
console.log(`${c.cyan}[${i + 1}/${collections.length}]${c.reset} ${c.bold}${col.name}${c.reset}`);
console.log(`${c.dim} Path: ${col.pwd}${c.reset}`);
console.log(`${c.dim} Pattern: ${col.glob_pattern}${c.reset}`);
// Check if this is a git repository
// Execute custom update command if specified in YAML
const yamlCol = getCollectionFromYaml(col.name);
if (yamlCol?.update) {
console.log(`${c.dim} Running update command: ${yamlCol.update}${c.reset}`);
try {
const proc = Bun.spawn(["/usr/bin/env", "bash", "-c", yamlCol.update], {
cwd: col.pwd,
stdout: "pipe",
stderr: "pipe",
});
const output = await new Response(proc.stdout).text();
const errorOutput = await new Response(proc.stderr).text();
const exitCode = await proc.exited;
if (output.trim()) {
console.log(output.trim().split('\n').map(l => ` ${l}`).join('\n'));
}
if (errorOutput.trim()) {
console.log(errorOutput.trim().split('\n').map(l => ` ${l}`).join('\n'));
}
if (exitCode !== 0) {
console.log(`${c.yellow}✗ Update command failed with exit code ${exitCode}${c.reset}`);
process.exit(exitCode);
}
} catch (err) {
console.log(`${c.yellow}✗ Update command failed: ${err}${c.reset}`);
process.exit(1);
}
}
// Check if this is a git repository (for legacy support)
const gitDir = `${col.pwd}/.git`;
let isGitRepo = false;
@ -559,8 +593,8 @@ async function updateCollections(pullFirst: boolean = false): Promise<void> {
if (isGitRepo) {
console.log(`${c.dim} Git repository detected${c.reset}`);
// Execute git pull if requested
if (pullFirst) {
// Execute git pull if requested via --pull flag
if (pullFirst && !yamlCol?.update) {
console.log(`${c.dim} Running git pull...${c.reset}`);
try {
const result = await $`cd ${col.pwd} && git pull`.quiet();