Files
cc-1c-skills/.claude/skills/web-test/scripts/cli/test-runner/suite-root.mjs
T

56 lines
2.5 KiB
JavaScript

// web-test cli/test-runner/suite-root v1.0 — locate the suite root above a given test path
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { existsSync, statSync } from 'fs';
import { resolve, dirname } from 'path';
// Files that MARK a suite root. Both count, not just the config: `webtest.config.mjs` is
// optional (a single-URL suite may pass --url= instead), and a suite that ships only
// `_hooks.mjs` must still be found — otherwise its stand preparation is silently skipped,
// which is worse than any URL error.
const MARKERS = ['webtest.config.mjs', '_hooks.mjs'];
// Files that BOUND the climb. A boundary never selects a root — it only stops the search,
// so a wrong boundary degrades to "root not found" (= the pre-v1.9 behaviour plus a clear
// message) and can never produce a wrong root. `package.json` is deliberately absent: it
// occurs nested and would stop the climb below a legitimate suite root.
const BOUNDARIES = ['.git', '.v8-project.json'];
const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
/**
* Walk up from `startPath` looking for a suite root.
*
* @param {string} startPath A test file or directory (absolute or cwd-relative).
* @param {{cwd?: string}} [opts]
* @returns {{root: string, marker: string} | null} null when no marker was found within bounds.
*
* Stops after examining the first directory that contains `.git` / `.v8-project.json`
* (that directory IS examined for markers), or — when neither is met — after examining `cwd`.
* A path outside `cwd` degenerates to the filesystem root; the marker requirement still
* makes a wrong hit unlikely, and the resolved root is printed in the run banner.
*/
export function findSuiteRoot(startPath, { cwd = process.cwd() } = {}) {
const full = resolve(startPath);
let dir = isDir(full) ? full : dirname(full);
const cwdAbs = resolve(cwd);
while (true) {
for (const m of MARKERS) {
if (existsSync(resolve(dir, m))) return { root: dir, marker: m };
}
const atBoundary = BOUNDARIES.some(b => existsSync(resolve(dir, b))) || dir === cwdAbs;
const parent = dirname(dir);
if (atBoundary || parent === dir) return null;
dir = parent;
}
}
/**
* The directory a path contributes to root resolution — its own dir for a file, itself for
* a directory. Also the fallback root when no marker is found (pre-v1.9 behaviour).
*/
export function startDirOf(testPath) {
const full = resolve(testPath);
return isDir(full) ? full : dirname(full);
}