fix(uri): include index in custom qmd links
This commit is contained in:
parent
c2f3a40372
commit
8404cc3bb1
@ -110,6 +110,7 @@ enableProductionMode();
|
||||
|
||||
let store: ReturnType<typeof createStore> | null = null;
|
||||
let storeDbPathOverride: string | undefined;
|
||||
let currentIndexName = "index";
|
||||
|
||||
function getStore(): ReturnType<typeof createStore> {
|
||||
if (!store) {
|
||||
@ -160,6 +161,10 @@ function getDbPath(): string {
|
||||
return store?.dbPath ?? storeDbPathOverride ?? getDefaultDbPath();
|
||||
}
|
||||
|
||||
function getActiveIndexName(): string {
|
||||
return currentIndexName;
|
||||
}
|
||||
|
||||
function setIndexName(name: string | null): void {
|
||||
let normalizedName = name;
|
||||
// Normalize relative paths to prevent malformed database paths
|
||||
@ -170,6 +175,7 @@ function setIndexName(name: string | null): void {
|
||||
// Replace path separators with underscores to create a valid filename
|
||||
normalizedName = absolutePath.replace(/\//g, '_').replace(/^_/, '');
|
||||
}
|
||||
currentIndexName = normalizedName || "index";
|
||||
storeDbPathOverride = normalizedName ? getDefaultDbPath(normalizedName) : undefined;
|
||||
// Reset open handle so next use opens the new index
|
||||
closeDb();
|
||||
@ -818,8 +824,6 @@ function contextRemove(pathArg: string): void {
|
||||
}
|
||||
|
||||
function getDocument(filename: string, fromLine?: number, maxLines?: number, lineNumbers?: boolean): void {
|
||||
const db = getDb();
|
||||
|
||||
// Parse :linenum suffix from filename (e.g., "file.md:100")
|
||||
let inputPath = filename;
|
||||
const colonMatch = inputPath.match(/:(\d+)$/);
|
||||
@ -831,6 +835,14 @@ function getDocument(filename: string, fromLine?: number, maxLines?: number, lin
|
||||
}
|
||||
}
|
||||
|
||||
const parsedIndexPath = isVirtualPath(inputPath) ? parseVirtualPath(inputPath) : null;
|
||||
if (parsedIndexPath?.indexName) {
|
||||
setIndexName(parsedIndexPath.indexName);
|
||||
setConfigIndexName(parsedIndexPath.indexName);
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
|
||||
// Handle docid lookup (#abc123, abc123, "#abc123", "abc123", etc.)
|
||||
if (isDocid(inputPath)) {
|
||||
const docidMatch = findDocumentByDocid(db, inputPath);
|
||||
@ -842,7 +854,6 @@ function getDocument(filename: string, fromLine?: number, maxLines?: number, lin
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let doc: { collectionName: string; path: string; body: string } | null = null;
|
||||
let virtualPath: string;
|
||||
|
||||
@ -1925,7 +1936,18 @@ function outputResults(results: OutputRow[], query: string, opts: OutputOptions)
|
||||
}
|
||||
|
||||
// Helper to create qmd:// URI from displayPath
|
||||
const toQmdPath = (displayPath: string) => `qmd://${displayPath}`;
|
||||
const toQmdPath = (displayPath: string) => {
|
||||
const [collectionName, ...segments] = displayPath.split("/");
|
||||
if (!collectionName || segments.length === 0) {
|
||||
return `qmd://${displayPath}`;
|
||||
}
|
||||
const indexName = getActiveIndexName();
|
||||
return buildVirtualPath(
|
||||
collectionName,
|
||||
segments.join("/"),
|
||||
indexName === "index" ? undefined : indexName,
|
||||
);
|
||||
};
|
||||
|
||||
if (opts.format === "json") {
|
||||
// JSON output for LLM consumption
|
||||
|
||||
11
src/store.ts
11
src/store.ts
@ -566,6 +566,7 @@ export function getRealPath(path: string): string {
|
||||
export type VirtualPath = {
|
||||
collectionName: string;
|
||||
path: string; // relative path within collection
|
||||
indexName?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -609,22 +610,26 @@ export function normalizeVirtualPath(input: string): string {
|
||||
export function parseVirtualPath(virtualPath: string): VirtualPath | null {
|
||||
// Normalize the path first
|
||||
const normalized = normalizeVirtualPath(virtualPath);
|
||||
const [pathPart = normalized, queryString = ""] = normalized.split("?");
|
||||
|
||||
// Match: qmd://collection-name[/optional-path]
|
||||
// Allows: qmd://name, qmd://name/, qmd://name/path
|
||||
const match = normalized.match(/^qmd:\/\/([^\/]+)\/?(.*)$/);
|
||||
const match = pathPart.match(/^qmd:\/\/([^\/]+)\/?(.*)$/);
|
||||
if (!match?.[1]) return null;
|
||||
const indexName = new URLSearchParams(queryString).get("index")?.trim() || undefined;
|
||||
return {
|
||||
collectionName: match[1],
|
||||
path: match[2] ?? '', // Empty string for collection root
|
||||
...(indexName ? { indexName } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a virtual path from collection name and relative path.
|
||||
*/
|
||||
export function buildVirtualPath(collectionName: string, path: string): string {
|
||||
return `qmd://${collectionName}/${path}`;
|
||||
export function buildVirtualPath(collectionName: string, path: string, indexName?: string): string {
|
||||
const base = `qmd://${collectionName}/${path}`;
|
||||
return indexName ? `${base}?index=${encodeURIComponent(indexName)}` : base;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1130,6 +1130,42 @@ describe("search output formats", () => {
|
||||
expect(result.file).not.toMatch(/^\/home\//);
|
||||
});
|
||||
|
||||
test("custom-index search links include ?index= and can be passed back to qmd get", async () => {
|
||||
const env = await createIsolatedTestEnv("custom-index-links");
|
||||
const customColl = "fixtures-alt";
|
||||
const customIndex = "release-notes";
|
||||
const customCacheDir = join(testDir, `cache-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
||||
await mkdir(customCacheDir, { recursive: true });
|
||||
|
||||
const sharedEnv = {
|
||||
INDEX_PATH: "",
|
||||
XDG_CACHE_HOME: customCacheDir,
|
||||
};
|
||||
|
||||
const addResult = await runQmd(
|
||||
["--index", customIndex, "collection", "add", fixturesDir, "--name", customColl],
|
||||
{ dbPath: env.dbPath, configDir: env.configDir, env: sharedEnv }
|
||||
);
|
||||
expect(addResult.exitCode).toBe(0);
|
||||
|
||||
const searchResult = await runQmd(
|
||||
["--index", customIndex, "search", "test", "--json", "-n", "1"],
|
||||
{ dbPath: env.dbPath, configDir: env.configDir, env: sharedEnv }
|
||||
);
|
||||
expect(searchResult.exitCode).toBe(0);
|
||||
|
||||
const results = JSON.parse(searchResult.stdout);
|
||||
const file = results[0]?.file;
|
||||
expect(file).toMatch(new RegExp(`^qmd://${customColl}/.+\\?index=${customIndex}$`));
|
||||
|
||||
const getResult = await runQmd(
|
||||
["get", file, "-l", "2"],
|
||||
{ dbPath: env.dbPath, configDir: env.configDir, env: sharedEnv }
|
||||
);
|
||||
expect(getResult.exitCode).toBe(0);
|
||||
expect(getResult.stdout.trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("search --files includes qmd:// path, docid, and context", async () => {
|
||||
const { stdout, exitCode } = await runQmd(["search", "test", "--files", "-n", "1"], { dbPath: localDbPath, configDir: localConfigDir });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
@ -3154,6 +3154,14 @@ describe("parseVirtualPath", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("parses qmd:// paths with index query parameters", () => {
|
||||
expect(parseVirtualPath("qmd://collection/path.md?index=docs-v2")).toEqual({
|
||||
collectionName: "collection",
|
||||
path: "path.md",
|
||||
indexName: "docs-v2",
|
||||
});
|
||||
});
|
||||
|
||||
test("returns null for non-virtual paths", () => {
|
||||
expect(parseVirtualPath("/absolute/path.md")).toBe(null);
|
||||
expect(parseVirtualPath("~/home/path.md")).toBe(null);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user