From 9e8eefb780bf0da8abcea03ae91efb08fe6e8325 Mon Sep 17 00:00:00 2001 From: cocoon Date: Tue, 7 Apr 2026 23:15:33 +0800 Subject: [PATCH] fix(config): normalize windows index names --- src/cli/qmd.ts | 11 ++--------- src/collections.ts | 27 +++++++++++++++++---------- test/collections-config.test.ts | 14 +++++++++++++- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/cli/qmd.ts b/src/cli/qmd.ts index a09ffb33..cc0ea56f 100755 --- a/src/cli/qmd.ts +++ b/src/cli/qmd.ts @@ -96,6 +96,7 @@ import { setGlobalContext, listAllContexts, setConfigIndexName, + normalizeIndexName, loadConfig, } from "../collections.js"; import { getEmbeddedQmdSkillContent, getEmbeddedQmdSkillFiles } from "../embedded-skills.js"; @@ -161,15 +162,7 @@ function getDbPath(): string { } function setIndexName(name: string | null): void { - let normalizedName = name; - // Normalize relative paths to prevent malformed database paths - if (name && name.includes('/')) { - const { resolve } = require('path'); - const { cwd } = require('process'); - const absolutePath = resolve(cwd(), name); - // Replace path separators with underscores to create a valid filename - normalizedName = absolutePath.replace(/\//g, '_').replace(/^_/, ''); - } + const normalizedName = name ? normalizeIndexName(name) : name; storeDbPathOverride = normalizedName ? getDefaultDbPath(normalizedName) : undefined; // Reset open handle so next use opens the new index closeDb(); diff --git a/src/collections.ts b/src/collections.ts index e68ff65b..83f30df8 100644 --- a/src/collections.ts +++ b/src/collections.ts @@ -70,6 +70,22 @@ let currentIndexName: string = "index"; // SDK mode: optional in-memory config or custom config path let configSource: { type: 'file'; path?: string } | { type: 'inline'; config: CollectionConfig } = { type: 'file' }; +export function normalizeIndexName(name: string): string { + const isWindowsAbsolute = /^[a-zA-Z]:[\\/]/.test(name) || /^\\\\/.test(name); + const isPathLike = isWindowsAbsolute || /[\\/]/.test(name); + + if (!isPathLike) return name; + + let absolutePath = name; + if (!isWindowsAbsolute) { + const { resolve } = require("path"); + const { cwd } = require("process"); + absolutePath = resolve(cwd(), name); + } + + return absolutePath.replace(/[:\\/]+/g, "_").replace(/^_+/, ""); +} + /** * Set the config source for SDK mode. * - File path: load/save from a specific YAML file @@ -99,16 +115,7 @@ export function setConfigSource(source?: { configPath?: string; config?: Collect * Config file will be ~/.config/qmd/{indexName}.yml */ export function setConfigIndexName(name: string): void { - // Resolve relative paths to absolute paths and sanitize for use as filename - if (name.includes('/')) { - const { resolve } = require('path'); - const { cwd } = require('process'); - const absolutePath = resolve(cwd(), name); - // Replace path separators with underscores to create a valid filename - currentIndexName = absolutePath.replace(/\//g, '_').replace(/^_/, ''); - } else { - currentIndexName = name; - } + currentIndexName = normalizeIndexName(name); } function getConfigDir(): string { diff --git a/test/collections-config.test.ts b/test/collections-config.test.ts index b6b15fe4..40eb347f 100644 --- a/test/collections-config.test.ts +++ b/test/collections-config.test.ts @@ -8,7 +8,7 @@ import { describe, test, expect, beforeEach, afterEach } from "vitest"; import { join } from "path"; import { homedir } from "os"; -import { getConfigPath, setConfigIndexName } from "../src/collections.js"; +import { getConfigPath, normalizeIndexName, setConfigIndexName } from "../src/collections.js"; // Save/restore env vars around each test let savedEnv: Record; @@ -71,4 +71,16 @@ describe("getConfigDir via getConfigPath", () => { setConfigIndexName("myindex"); expect(getConfigPath()).toBe(join("/xdg/config", "qmd", "myindex.yml")); }); + + test("normalizes Windows absolute paths into safe index names", () => { + expect(normalizeIndexName("C:\\Users\\axulo\\Documents\\ppttest")).toBe( + "C_Users_axulo_Documents_ppttest" + ); + }); + + test("uses a normalized filename when QMD_CONFIG_DIR is set and index name is a Windows path", () => { + process.env.QMD_CONFIG_DIR = "/custom/qmd-config"; + setConfigIndexName("C:\\Users\\axulo\\Documents\\ppttest"); + expect(getConfigPath()).toBe(join("/custom/qmd-config", "C_Users_axulo_Documents_ppttest.yml")); + }); });