fix(router-gate): stream A path-normalization — $& replacement, narrow catch, BOM/EOF, docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-29 19:48:49 +03:00
parent e0f6c52f37
commit 52e1cfec1a
2 changed files with 44 additions and 7 deletions
+20 -6
View File
@@ -1,4 +1,4 @@
// tools/path-normalization.mjs
// tools/path-normalization.mjs
/**
* Path normalization — router-gate v4 spec §3.1.1.
* + glob-matcher util (used by skill-scope-verifier, tdd-real-test-verifier).
@@ -25,8 +25,9 @@ export function expandEnvVars(target, env) {
if (val === undefined) continue;
out = out.split(`%${name}%`).join(val);
out = out.split(`\${${name}}`).join(val);
// bare $VAR — only when followed by non-word boundary
out = out.replace(new RegExp(`\\$${name}(?![A-Za-z0-9_])`, 'g'), val);
// bare $VAR — only when followed by non-word boundary.
// Use a function replacer so `val` is inserted literally (avoids $& / $' / $` replacement-pattern misinterpretation).
out = out.replace(new RegExp(`\\$${name}(?![A-Za-z0-9_])`, 'g'), () => val);
}
return out;
}
@@ -35,6 +36,7 @@ export function caseFold(p, platform) {
return platform === 'win32' ? p.toLowerCase() : p;
}
// NOTE: `pattern` must use forward slashes. For cross-platform path matching use `globMatch` instead.
export function globToRegExp(pattern) {
let re = '';
for (let i = 0; i < pattern.length; i++) {
@@ -63,6 +65,17 @@ export function globMatch(pathStr, pattern) {
return globToRegExp(norm(pattern)).test(norm(pathStr));
}
/**
* Normalize a path: expand ~, expand whitelisted env vars, resolve, realpath, case-fold.
*
* @param {string} target - Raw path (may contain ~ or $VAR).
* @param {object} [opts]
* @param {string} [opts.homedir] - Override home directory (default: os.homedir()).
* @param {object} [opts.env] - Override environment map (default: process.env).
* @param {string} [opts.platform] - Override platform string (default: process.platform).
* @param {Function} [opts.realpath] - Injectable realpath (default: fs.realpathSync) — used for test determinism.
* @param {Function} [opts.resolve] - Injectable path.resolve (default: path.resolve) — injectable for cross-platform test determinism.
*/
export function pathNormalize(target, opts = {}) {
const {
homedir = os.homedir(),
@@ -77,8 +90,9 @@ export function pathNormalize(target, opts = {}) {
let real;
try {
real = realpath(resolved);
} catch {
real = resolved; // ENOENT — best-effort resolved path
} catch (e) {
if (e && e.code && e.code !== 'ENOENT') throw e; // surface real FS errors; fail-close handled by caller
real = resolved; // ENOENT — best-effort resolved path for unknown-state files
}
return caseFold(real, platform);
}
}