Răsfoiți Sursa

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>
Tobi Lutke 5 luni în urmă
părinte
comite
b4e60a08e5
2 a modificat fișierele cu 52 adăugiri și 8 ștergeri
  1. 12 2
      src/collections.ts
  2. 40 6
      src/qmd.ts

+ 12 - 2
src/collections.ts

@@ -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

+ 40 - 6
src/qmd.ts

@@ -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();