Test nix flake builds in CI (#487)

* Test nix flake builds in CI

* Update outdated bun.lock file

* fix: restore toLowerCase() in handelize and update tests

* Fix flake to use proper FODs

---------

Co-authored-by: Tobias Lütke <tobi@shopify.com>
This commit is contained in:
Surma 2026-04-05 21:59:27 +01:00 committed by GitHub
parent 828823d20a
commit 2de225c9e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 117 additions and 11 deletions

27
.github/workflows/nix.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Nix
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-flake:
name: Build flake (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build flake
run: nix build . --print-build-logs

View File

@ -13,13 +13,18 @@
chunking for code files.
- `qmd status` now shows AST grammar availability.
- SDK: `chunkStrategy` option on `embed()` and `search()` methods.
- GitHub Actions workflow to build the Nix flake on Linux and macOS.
### Fixes
- Fix paths in nix flake
- Make the Nix flake's Bun dependency fetch a fixed-output derivation so
sandboxed Linux builds can install dependencies offline.
- Sync stale `bun.lock` (`better-sqlite3` 11.x → 12.x). CI and release
script now use `--frozen-lockfile` to prevent recurrence. #386
(thanks @Mic92)
- Sync duplicated `handelize()` test expectations with the restored lowercase
behavior.
## [2.0.1] - 2026-03-10

View File

@ -11,6 +11,7 @@
"node-llama-cpp": "^3.17.1",
"picomatch": "^4.0.0",
"sqlite-vec": "^0.1.7-alpha.2",
"web-tree-sitter": "0.26.7",
"yaml": "^2.8.2",
"zod": "4.2.1",
},
@ -25,6 +26,10 @@
"sqlite-vec-linux-arm64": "^0.1.7-alpha.2",
"sqlite-vec-linux-x64": "^0.1.7-alpha.2",
"sqlite-vec-windows-x64": "^0.1.7-alpha.2",
"tree-sitter-go": "0.23.4",
"tree-sitter-python": "0.23.4",
"tree-sitter-rust": "0.24.0",
"tree-sitter-typescript": "0.23.2",
},
"peerDependencies": {
"typescript": "^5.9.3",
@ -506,6 +511,8 @@
"node-api-headers": ["node-api-headers@1.8.0", "", {}, "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ=="],
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
"node-llama-cpp": ["node-llama-cpp@3.17.1", "", { "dependencies": { "@huggingface/jinja": "^0.5.5", "async-retry": "^1.3.3", "bytes": "^3.1.2", "chalk": "^5.6.2", "chmodrp": "^1.0.2", "cmake-js": "^8.0.0", "cross-spawn": "^7.0.6", "env-var": "^7.5.0", "filenamify": "^6.0.0", "fs-extra": "^11.3.0", "ignore": "^7.0.4", "ipull": "^3.9.5", "is-unicode-supported": "^2.1.0", "lifecycle-utils": "^3.1.1", "log-symbols": "^7.0.1", "nanoid": "^5.1.6", "node-addon-api": "^8.5.0", "ora": "^9.3.0", "pretty-ms": "^9.3.0", "proper-lockfile": "^4.1.2", "semver": "^7.7.1", "simple-git": "^3.32.2", "slice-ansi": "^8.0.0", "stdout-update": "^4.0.1", "strip-ansi": "^7.1.2", "validate-npm-package-name": "^7.0.2", "which": "^6.0.1", "yargs": "^17.7.2" }, "optionalDependencies": { "@node-llama-cpp/linux-arm64": "3.17.1", "@node-llama-cpp/linux-armv7l": "3.17.1", "@node-llama-cpp/linux-x64": "3.17.1", "@node-llama-cpp/linux-x64-cuda": "3.17.1", "@node-llama-cpp/linux-x64-cuda-ext": "3.17.1", "@node-llama-cpp/linux-x64-vulkan": "3.17.1", "@node-llama-cpp/mac-arm64-metal": "3.17.1", "@node-llama-cpp/mac-x64": "3.17.1", "@node-llama-cpp/win-arm64": "3.17.1", "@node-llama-cpp/win-x64": "3.17.1", "@node-llama-cpp/win-x64-cuda": "3.17.1", "@node-llama-cpp/win-x64-cuda-ext": "3.17.1", "@node-llama-cpp/win-x64-vulkan": "3.17.1" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"], "bin": { "node-llama-cpp": "dist/cli/cli.js", "nlc": "dist/cli/cli.js" } }, "sha512-f+eYXag3kFeMwLrTTSTtyt+4p2etJGvTPXEdipYy7EqSZha9ZFBpGYNftxZwbdTiqh/qyNSe3TeZve5tkq5OPQ=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@ -678,6 +685,16 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tree-sitter-go": ["tree-sitter-go@0.23.4", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.21.1" }, "optionalPeers": ["tree-sitter"] }, "sha512-iQaHEs4yMa/hMo/ZCGqLfG61F0miinULU1fFh+GZreCRtKylFLtvn798ocCZjO2r/ungNZgAY1s1hPFyAwkc7w=="],
"tree-sitter-javascript": ["tree-sitter-javascript@0.23.1", "", { "dependencies": { "node-addon-api": "^8.2.2", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.21.1" }, "optionalPeers": ["tree-sitter"] }, "sha512-/bnhbrTD9frUYHQTiYnPcxyHORIw157ERBa6dqzaKxvR/x3PC4Yzd+D1pZIMS6zNg2v3a8BZ0oK7jHqsQo9fWA=="],
"tree-sitter-python": ["tree-sitter-python@0.23.4", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.21.1" }, "optionalPeers": ["tree-sitter"] }, "sha512-MbmUAl7y5UCUWqHscHke7DdRDwQnVNMNKQYQc4Gq2p09j+fgPxaU8JVsuOI/0HD3BSEEe5k9j3xmdtIWbDtDgw=="],
"tree-sitter-rust": ["tree-sitter-rust@0.24.0", "", { "dependencies": { "node-addon-api": "^8.2.2", "node-gyp-build": "^4.8.4" }, "peerDependencies": { "tree-sitter": "^0.22.1" }, "optionalPeers": ["tree-sitter"] }, "sha512-NWemUDf629Tfc90Y0Z55zuwPCAHkLxWnMf2RznYu4iBkkrQl2o/CHGB7Cr52TyN5F1DAx8FmUnDtCy9iUkXZEQ=="],
"tree-sitter-typescript": ["tree-sitter-typescript@0.23.2", "", { "dependencies": { "node-addon-api": "^8.2.2", "node-gyp-build": "^4.8.2", "tree-sitter-javascript": "^0.23.1" }, "peerDependencies": { "tree-sitter": "^0.21.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-e04JUUKxTT53/x3Uq1zIL45DoYKVfHH4CZqwgZhPg5qYROl5nQjV+85ruFzFGZxu+QeFVbRTPDRnqL9UbU4VeA=="],
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
@ -706,6 +723,8 @@
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
"web-tree-sitter": ["web-tree-sitter@0.26.7", "", {}, "sha512-KiZhelTvBA/ziUHEO7Emb75cGVAq8iGZNabYaZm53Zpy50NsXyOW+xSHlwHt5CVg/TRPZBfeVLTTobF0LjFJ1w=="],
"which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],

View File

@ -18,6 +18,55 @@
];
});
nodeModulesHashes = {
x86_64-linux = "sha256-Hymzuiid76j0LbDRACYlRQ2UVxQp7t9xg4nH37l0Keg=";
aarch64-darwin = "sha256-/9kp5mNrI7hVR137DRpSuZHnl1RL/wFu2hKyzXW66TU=";
# Populate these on first build for additional hosts if/when needed.
aarch64-linux = pkgs.lib.fakeHash;
x86_64-darwin = pkgs.lib.fakeHash;
};
nodeModules = pkgs.stdenvNoCC.mkDerivation {
pname = "qmd-node-modules";
version = "1.0.0";
src = ./.;
impureEnvVars = pkgs.lib.fetchers.proxyImpureEnvVars ++ [
"GIT_PROXY_COMMAND"
"SOCKS_SERVER"
];
nativeBuildInputs = [
pkgs.bun
];
dontConfigure = true;
buildPhase = ''
export HOME=$(mktemp -d)
bun install \
--backend copyfile \
--frozen-lockfile \
--ignore-scripts \
--no-progress \
--production
'';
installPhase = ''
mkdir -p $out
cp -R node_modules $out/
'';
dontFixup = true;
outputHash = nodeModulesHashes.${system};
outputHashAlgo = "sha256";
outputHashMode = "recursive";
};
qmd = pkgs.stdenv.mkDerivation {
pname = "qmd";
version = "1.0.0";
@ -27,6 +76,8 @@
nativeBuildInputs = [
pkgs.bun
pkgs.makeWrapper
pkgs.nodejs
pkgs.node-gyp
pkgs.python3 # needed by node-gyp to compile better-sqlite3
] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
pkgs.darwin.cctools # provides libtool needed by node-gyp on macOS
@ -36,7 +87,11 @@
buildPhase = ''
export HOME=$(mktemp -d)
bun install --frozen-lockfile
cp -R ${nodeModules}/node_modules ./
chmod -R u+w node_modules
(cd node_modules/better-sqlite3 && node-gyp rebuild --release)
'';
installPhase = ''

View File

@ -115,14 +115,14 @@ describe("cleanupOrphanedVectors", () => {
// =============================================================================
describe("handelize", () => {
test("preserves original case", () => {
expect(handelize("README.md")).toBe("README.md");
expect(handelize("MyFile.MD")).toBe("MyFile.MD");
test("converts to lowercase", () => {
expect(handelize("README.md")).toBe("readme.md");
expect(handelize("MyFile.MD")).toBe("myfile.md");
});
test("preserves folder structure", () => {
expect(handelize("a/b/c/d.md")).toBe("a/b/c/d.md");
expect(handelize("docs/api/README.md")).toBe("docs/api/README.md");
expect(handelize("docs/api/README.md")).toBe("docs/api/readme.md");
});
test("replaces non-word characters with dash", () => {
@ -152,7 +152,7 @@ describe("handelize", () => {
test("handles complex real-world meeting notes", () => {
const complexName = "Money Movement Licensing Review - 20251119 10:25 EST - Notes by Gemini.md";
const result = handelize(complexName);
expect(result).toBe("Money-Movement-Licensing-Review-2025-11-19-10-25-EST-Notes-by-Gemini.md");
expect(result).toBe("money-movement-licensing-review-2025-11-19-10-25-est-notes-by-gemini.md");
expect(result).not.toContain(" ");
expect(result).not.toContain("");
expect(result).not.toContain(":");
@ -160,7 +160,7 @@ describe("handelize", () => {
test("handles unicode characters", () => {
expect(handelize("日本語.md")).toBe("日本語.md");
expect(handelize("Зоны и проекты.md")).toBe("Зоны-и-проекты.md");
expect(handelize("Зоны и проекты.md")).toBe("зоны-и-проекты.md");
expect(handelize("café-notes.md")).toBe("café-notes.md");
expect(handelize("naïve.md")).toBe("naïve.md");
expect(handelize("日本語-notes.md")).toBe("日本語-notes.md");
@ -182,13 +182,13 @@ describe("handelize", () => {
test("handles dates and times in filenames", () => {
expect(handelize("meeting-2025-01-15.md")).toBe("meeting-2025-01-15.md");
expect(handelize("notes 2025/01/15.md")).toBe("notes-2025/01/15.md");
expect(handelize("call_10:30_AM.md")).toBe("call-10-30-AM.md");
expect(handelize("call_10:30_AM.md")).toBe("call-10-30-am.md");
});
test("handles special project naming patterns", () => {
expect(handelize("PROJECT_ABC_v2.0.md")).toBe("PROJECT-ABC-v2.0.md");
expect(handelize("[WIP] Feature Request.md")).toBe("WIP-Feature-Request.md");
expect(handelize("(DRAFT) Proposal v1.md")).toBe("DRAFT-Proposal-v1.md");
expect(handelize("PROJECT_ABC_v2.0.md")).toBe("project-abc-v2.0.md");
expect(handelize("[WIP] Feature Request.md")).toBe("wip-feature-request.md");
expect(handelize("(DRAFT) Proposal v1.md")).toBe("draft-proposal-v1.md");
});
test("handles symbol-only route filenames", () => {