Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/external-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ jobs:
{ "name": "@ff-labs/fff-bin-linux-x64-musl", "version": "0.0.0" }
JSON

# fff-node builds with bun; the npm `bun` package ships musl binaries
- name: Install bun
run: npm install -g bun

- name: Build fff-node
working-directory: packages/fff-node
run: npm run build
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,8 @@ jobs:
node-version: "25"
registry-url: "https://registry.npmjs.org"

- uses: oven-sh/setup-bun@v2

Comment on lines +713 to +714

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- release workflow excerpt ---'
sed -n '670,735p' .github/workflows/release.yaml

printf '%s\n' '--- setup-bun references ---'
rg -n -C 3 'oven-sh/setup-bun|bun-version|cache:' .github package.json bun.lockb bun.lock 2>/dev/null || true

printf '%s\n' '--- version/install ordering ---'
rg -n -C 5 'npm version|yarn version|pnpm version|bun install|frozen-lockfile|packageManager|version:' .github/workflows/release.yaml package.json bunfig.toml 2>/dev/null || true

printf '%s\n' '--- tracked lockfiles and manifests ---'
git ls-files | rg '(^|/)(package\.json|bun\.lockb?|bunfig\.toml|.*lock.*)$' || true

Repository: dmtrKovalenko/fff

Length of output: 10681


🏁 Script executed:

#!/bin/bash
set -eu

sed -n '670,735p' .github/workflows/release.yaml
rg -n -C 3 'oven-sh/setup-bun|bun-version|cache:' .github package.json bun.lockb bun.lock 2>/dev/null || true
rg -n -C 5 'npm version|yarn version|pnpm version|bun install|frozen-lockfile|packageManager|version:' .github/workflows/release.yaml package.json bunfig.toml 2>/dev/null || true
git ls-files | rg '(^|/)(package\.json|bun\.lockb?|bunfig\.toml|.*lock.*)$' || true

Repository: dmtrKovalenko/fff

Length of output: 10546


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- npm publish workflow ---'
sed -n '735,790p' .github/workflows/release.yaml

printf '%s\n' '--- version mutation implementation ---'
rg -n -C 8 'set-npm-version|set_npm_version|npm.*version|VERSION' Makefile scripts .github/workflows/release.yaml

printf '%s\n' '--- Bun package manifest and lockfile header ---'
cat -n packages/fff-bun/package.json
sed -n '1,80p' packages/fff-bun/bun.lock

printf '%s\n' '--- upstream setup-bun action metadata ---'
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/action.yml
printf '%s\n' '--- upstream setup-bun README cache/version references ---'
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/README.md | rg -n -C 3 'bun-version|cache|pin|commit' | head -120

Repository: dmtrKovalenko/fff

Length of output: 33276


🏁 Script executed:

#!/bin/bash
set -eu

sed -n '735,790p' .github/workflows/release.yaml
rg -n -C 8 'set-npm-version|set_npm_version|npm.*version|VERSION' Makefile scripts .github/workflows/release.yaml
cat -n packages/fff-bun/package.json
sed -n '1,80p' packages/fff-bun/bun.lock
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/action.yml
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/README.md \
  | rg -n -C 3 'bun-version|cache|pin|commit' | head -120

Repository: dmtrKovalenko/fff

Length of output: 33056


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- root package metadata ---'
cat -n package.json

printf '%s\n' '--- setup-bun cache implementation references ---'
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/dist/setup/index.js \
  | rg -n -o '.{0,120}(no-cache|cache|tool-cache|RUNNER_TOOL_CACHE).{0,180}' | head -80 || true
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/dist/cache-save/index.js \
  | rg -n -o '.{0,120}(no-cache|cache|tool-cache|RUNNER_TOOL_CACHE).{0,180}' | head -80 || true

printf '%s\n' '--- zizmor cache-poisoning documentation ---'
curl -fsSL https://raw.githubusercontent.com/woodruffw/zizmor/main/docs/audits.md \
  | rg -n -C 8 'cache-poison|unpinned-uses|setup-bun' | head -160 || true

Repository: dmtrKovalenko/fff

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

cat -n package.json

curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/dist/setup/index.js \
  | rg -n -o '.{0,120}(no-cache|cache|tool-cache|RUNNER_TOOL_CACHE).{0,180}' | head -80 || true
curl -fsSL https://raw.githubusercontent.com/oven-sh/setup-bun/main/dist/cache-save/index.js \
  | rg -n -o '.{0,120}(no-cache|cache|tool-cache|RUNNER_TOOL_CACHE).{0,180}' | head -80 || true

curl -fsSL https://raw.githubusercontent.com/woodruffw/zizmor/main/docs/audits.md \
  | rg -n -C 8 'cache-poison|unpinned-uses|setup-bun' | head -160 || true

Repository: dmtrKovalenko/fff

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import json
from pathlib import Path

manifest = json.loads(Path("packages/fff-bun/package.json").read_text())
lock = json.loads(Path("packages/fff-bun/bun.lock").read_text())
workspace = lock["workspaces"][""]

version = "9.9.9-test"
mutated = json.loads(json.dumps(manifest))
mutated["version"] = version
for name in mutated.get("optionalDependencies", {}):
    mutated["optionalDependencies"][name] = version

locked_optional = workspace.get("optionalDependencies", {})
manifest_names = set(mutated.get("optionalDependencies", {}))
locked_names = set(locked_optional)

print("manifest version after make set-npm-version:", mutated["version"])
print("manifest optional dependency names:", sorted(manifest_names))
print("lockfile optional dependency names:", sorted(locked_names))
print("names missing from lockfile:", sorted(manifest_names - locked_names))
print("names only in lockfile:", sorted(locked_names - manifest_names))
print("manifest optional versions:", sorted(set(mutated["optionalDependencies"].values())))
print("lockfile optional versions:", sorted(set(locked_optional.values())))
print("version mismatch:", set(mutated["optionalDependencies"].values()) != set(locked_optional.values()))
PY

printf '%s\n' '--- Bun frozen-lockfile documentation ---'
curl -fsSL https://bun.sh/docs/pm/lockfile \
  | rg -n -C 4 'frozen|lockfile|package.json' | head -100 || true

Repository: dmtrKovalenko/fff

Length of output: 672


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import json
from pathlib import Path

manifest = json.loads(Path("packages/fff-bun/package.json").read_text())
lock = json.loads(Path("packages/fff-bun/bun.lock").read_text())
workspace = lock["workspaces"][""]

version = "9.9.9-test"
mutated = json.loads(json.dumps(manifest))
mutated["version"] = version
for name in mutated.get("optionalDependencies", {}):
    mutated["optionalDependencies"][name] = version

locked_optional = workspace.get("optionalDependencies", {})
manifest_names = set(mutated.get("optionalDependencies", {}))
locked_names = set(locked_optional)

print("manifest version after make set-npm-version:", mutated["version"])
print("manifest optional dependency names:", sorted(manifest_names))
print("lockfile optional dependency names:", sorted(locked_names))
print("names missing from lockfile:", sorted(manifest_names - locked_names))
print("names only in lockfile:", sorted(locked_names - manifest_names))
print("manifest optional versions:", sorted(set(mutated["optionalDependencies"].values())))
print("lockfile optional versions:", sorted(set(locked_optional.values())))
print("version mismatch:", set(mutated["optionalDependencies"].values()) != set(locked_optional.values()))
PY

curl -fsSL https://bun.sh/docs/pm/lockfile \
  | rg -n -C 4 'frozen|lockfile|package.json' | head -100 || true

Repository: dmtrKovalenko/fff

Length of output: 672


🏁 Script executed:

#!/bin/bash
set -u

python3 - <<'PY'
import re
from pathlib import Path

manifest_text = Path("packages/fff-bun/package.json").read_text()
lock_text = Path("packages/fff-bun/bun.lock").read_text()

def members(text, key):
    match = re.search(r'"' + re.escape(key) + r'"\s*:\s*\{(.*?)\n\s*\}', text, re.S)
    if not match:
        raise SystemExit(f"missing {key}")
    return dict(re.findall(r'"([^"]+)"\s*:\s*"([^"]+)"', match.group(1)))

manifest_optional = members(manifest_text, "optionalDependencies")
lock_optional = members(lock_text, "optionalDependencies")

version = "9.9.9-test"
manifest_names = set(manifest_optional)
mutated_versions = {name: version for name in manifest_names}

print("manifest optional dependency names:", sorted(manifest_names))
print("lockfile optional dependency names:", sorted(lock_optional))
print("names missing from lockfile:", sorted(manifest_names - set(lock_optional)))
print("names only in lockfile:", sorted(set(lock_optional) - manifest_names))
print("manifest optional versions after mutation:", sorted(set(mutated_versions.values())))
print("lockfile optional versions:", sorted(set(lock_optional.values())))
print("version mismatch:", set(mutated_versions.values()) != set(lock_optional.values()))
PY

printf '%s\n' '--- Bun frozen-lockfile documentation ---'
curl -fsSL https://bun.sh/docs/pm/lockfile 2>/dev/null \
  | rg -n -C 4 'frozen|lockfile|package.json' | head -100 || true

Repository: dmtrKovalenko/fff

Length of output: 50374


🏁 Script executed:

sed -n '1,30p' /tmp/coderabbit-shell-logs/shell-output-H1S6f1

Repository: dmtrKovalenko/fff

Length of output: 50373


Pin Bun and disable its cache before publishing.

oven-sh/setup-bun@v2 uses a mutable tag, installs latest, and enables its executable cache by default. Pin the action to a commit, set bun-version: "1.3.9", and use no-cache: true.

Also fix the stale packages/fff-bun/bun.lock: make set-npm-version changes dependency versions before bun install --frozen-lockfile, while the lockfile still contains 0.0.0 dependencies with different names.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 713-713: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yaml around lines 713 - 714, Update the release
workflow’s oven-sh/setup-bun step to use an immutable commit reference, set
bun-version to "1.3.9", and enable no-cache. Also regenerate
packages/fff-bun/bun.lock so it matches the dependency versions and names
produced by make set-npm-version before bun install --frozen-lockfile runs.

Source: Linters/SAST tools

- name: Determine version
id: version
run: lua scripts/determine-version.lua
Expand Down Expand Up @@ -747,6 +749,8 @@ jobs:
make set-npm-version PKG=packages/fff-bun VERSION="$VERSION"

cd packages/fff-bun
bun install --frozen-lockfile
bun run build
npm publish --tag "$TAG" --access public --provenance

- name: Publish Node.js package
Expand Down
15 changes: 10 additions & 5 deletions packages/fff-bun/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
"private": false,
"description": "High-performance fuzzy file finder for Bun - perfect for LLM agent tools",
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
}
"types": "./dist/index.d.ts",
"bun": "./src/index.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": [
"dist",
"src",
"examples"
],
"scripts": {
"build": "bun build ./src/index.ts --format=esm --target=bun --sourcemap=external --outdir dist && tsc -p tsconfig.build.json",
"test": "bun test test/",
"typecheck": "tsc --noEmit",
"demo": "bun ./examples/search.ts"
Expand Down
10 changes: 10 additions & 0 deletions packages/fff-bun/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"emitDeclarationOnly": true,
"noEmit": false
},
"include": ["src/**/*"]
}
19 changes: 13 additions & 6 deletions packages/fff-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@
"private": false,
"description": "High-performance fuzzy file finder for Node.js - perfect for LLM agent tools",
"type": "module",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/src/index.js",
"types": "./dist/src/index.d.ts"
}
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"build": "npm run build:esm && npm run build:cjs && npm run build:types",
"build:esm": "bun build ./src/index.ts --format=esm --target=node --external ffi-rs --sourcemap=external --outdir dist",
"build:cjs": "bun build ./src/index.ts --format=cjs --target=node --external ffi-rs --sourcemap=external --outdir dist --entry-naming \"[name].cjs\"",
"build:types": "tsc -p tsconfig.build.json",
"test": "node test/e2e.mjs && node test/watch.mjs",
"typecheck": "tsc --noEmit"
},
Expand Down
5 changes: 4 additions & 1 deletion packages/fff-node/src/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { getLibFilename, getNpmPackageName } from "./platform.js";
* Get the current file's directory
*/
function getCurrentDir(): string {
// CJS build: import.meta.url is inlined at bundle time, __dirname is the truth
if (typeof __dirname !== "undefined") return __dirname;

const url = import.meta.url;

if (url.startsWith("file://")) {
Expand All @@ -30,7 +33,7 @@ function getCurrentDir(): string {
function getPackageDir(): string {
const currentDir = getCurrentDir();
// In dev: src/ -> package root
// In dist: dist/src/ -> package root
// In dist: dist/ -> package root
// We look for package.json to find the actual root
let dir = currentDir;
for (let i = 0; i < 5; i++) {
Expand Down
4 changes: 2 additions & 2 deletions packages/fff-node/test/e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { after, before, describe, it } from "node:test";
import { strict as assert } from "node:assert";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { FileFinder } from "../dist/src/index.js";
import { FileFinder } from "../dist/index.js";

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, "..", "..", "..");
const normalizePath = (p) => p.replace(/\\/g, "/");

/** @type {import("../dist/src/finder.js").FileFinder | null} */
/** @type {import("../dist/finder.js").FileFinder | null} */
let finder = null;

describe("fff-node", { concurrency: 1 }, () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/fff-node/test/stress-515.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { existsSync } from "node:fs";
import { dirname, resolve } from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { FileFinder } from "../dist/src/index.js";
import { FileFinder } from "../dist/index.js";

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, "..", "..", "..");
Expand Down
6 changes: 3 additions & 3 deletions packages/fff-node/test/watch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { mkdtempSync, realpathSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, sep } from "node:path";
import { promisify } from "node:util";
import { FileFinder } from "../dist/src/index.js";
import { FileFinder } from "../dist/index.js";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

Expand All @@ -33,7 +33,7 @@ async function waitFor(cond, timeoutMs = 10_000) {
/** All events delivered to a batch-callback mock, flattened across calls. */
const deliveredEvents = (fn) => fn.mock.calls.flatMap((call) => call.arguments[0]);

/** @type {import("../dist/src/finder.js").FileFinder | null} */
/** @type {import("../dist/finder.js").FileFinder | null} */
let finder = null;
/** @type {string} */
let baseDir = "";
Expand Down Expand Up @@ -237,7 +237,7 @@ describe("fff-node watch", { concurrency: 1 }, () => {
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { FileFinder } from ${JSON.stringify(new URL("../dist/src/index.js", import.meta.url).href)};
import { FileFinder } from ${JSON.stringify(new URL("../dist/index.js", import.meta.url).href)};

const dir = mkdtempSync(join(tmpdir(), "fff-watch-exit-"));
writeFileSync(join(dir, "seed.txt"), "seed");
Expand Down
10 changes: 10 additions & 0 deletions packages/fff-node/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"emitDeclarationOnly": true,
"noEmit": false
},
"include": ["src/**/*"]
}
Loading