Files
wehub-resource-sync 1b279d4d10
e2e Tests / e2e (macos-latest) (push) Waiting to run
e2e Tests / e2e (windows-latest) (push) Waiting to run
Nix CI / check (push) Waiting to run
Python CI / Python bindings (macos-latest) (push) Waiting to run
Python CI / Python bindings (windows-latest) (push) Waiting to run
Build & Publish / Build Neovim aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Waiting to run
Build & Publish / Release (push) Blocked by required conditions
Build & Publish / Publish Python wheels to PyPI (push) Blocked by required conditions
Build & Publish / Publish Rust crates (push) Blocked by required conditions
Build & Publish / Publish npm packages (push) Blocked by required conditions
Rust CI / Test (macos-latest) (push) Waiting to run
Rust CI / Test (windows-latest) (push) Waiting to run
Rust CI / Fuzz Tests (macos-latest) (push) Waiting to run
Rust CI / Fuzz Tests (windows-latest) (push) Waiting to run
Build & Publish / Build MCP aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Waiting to run
Lua CI / lua-language-server type check (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Python CI / Python bindings (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 3s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 3s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 2s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 1s
Rust CI / cargo fmt (push) Failing after 1s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 4s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 1s
Rust CI / cargo clippy (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 1s
Stylua / Check lua files using Stylua (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 4s
e2e Tests / e2e (alpine-musl) (push) Successful in 58m54s
chore: import upstream snapshot with attribution
2026-07-13 12:24:42 +08:00

88 lines
3.2 KiB
TypeScript

import path from "node:path";
export function normalizePathConstraint(
pathConstraint: string,
cwd = process.cwd(),
): string | null {
let trimmed = pathConstraint.trim();
if (!trimmed) return trimmed;
if (path.isAbsolute(trimmed)) {
const relative = path.relative(cwd, trimmed).replaceAll(path.sep, "/");
if (relative === "") return null;
if (relative.startsWith("../") || relative === ".." || path.isAbsolute(relative)) {
throw new Error(
`Path constraint must be relative to the workspace: ${pathConstraint}`,
);
}
trimmed = relative;
}
if (trimmed === "." || trimmed === "./") return null;
// Strip a leading `./` so `./**/*.rs` and `**/*.rs` behave identically.
if (trimmed.startsWith("./")) trimmed = trimmed.slice(2);
// FFF's glob matcher can treat a hidden directory root glob such as
// `.agents/**` as empty, while the tool contract says this means "inside
// this directory". Collapse simple trailing recursive directory globs to the
// directory-prefix constraint understood by the parser. Keep real file globs
// such as `src/**/*.ts` unchanged.
const recursiveDir = trimmed.match(/^(.*)\/\*\*(?:\/\*)?$/);
if (recursiveDir) {
const dir = recursiveDir[1];
if (dir && !/[*?[{]/.test(dir)) return `${dir}/`;
}
// Already signals path-constraint syntax to the parser.
if (trimmed.startsWith("/") || trimmed.endsWith("/")) return trimmed;
// Globs (`*.ts`, `src/**/*.cc`, `{src,lib}`) are handled by the parser.
if (/[*?[{]/.test(trimmed)) return trimmed;
// Filename with extension (`main.rs`, `config.json`) → FilePath constraint.
const lastSegment = trimmed.split("/").pop() ?? "";
if (/\.[a-zA-Z][a-zA-Z0-9]{0,9}$/.test(lastSegment)) return trimmed;
// Bare directory prefix → append `/` so the parser sees a PathSegment.
return `${trimmed}/`;
}
// Exclusions are emitted as `!<constraint>` tokens, which the Rust parser
// understands (crates/fff-query-parser/src/parser.rs). We normalize each one
// the same way as the include path so bare dirs become PathSegment excludes.
// Tolerate callers passing already-negated forms like `!src/` by stripping
// the leading `!` before normalizing so we never double-negate (`!!src/`).
export function normalizeExcludes(
exclude: string | string[] | undefined,
cwd = process.cwd(),
): string[] {
if (!exclude) return [];
const list = Array.isArray(exclude) ? exclude : [exclude];
const out: string[] = [];
for (const raw of list) {
const parts = raw
.split(/[,\s]+/)
.map((s) => s.trim())
.filter(Boolean);
for (const p of parts) {
const stripped = p.startsWith("!") ? p.slice(1) : p;
const normalized = normalizePathConstraint(stripped, cwd);
if (normalized) out.push(`!${normalized}`);
}
}
return out;
}
export function buildQuery(
path: string | undefined,
pattern: string,
exclude?: string | string[],
cwd = process.cwd(),
): string {
const parts: string[] = [];
if (path) {
const pathConstraint = normalizePathConstraint(path, cwd);
if (pathConstraint) parts.push(pathConstraint);
}
parts.push(...normalizeExcludes(exclude, cwd));
parts.push(pattern);
return parts.join(" ");
}