mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
3378e07d50
* refactor(plugin-sdk): promote shared runtime primitives * test(codex): keep one attempt tools owner
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
// Qa Lab plugin module implements cli paths behavior.
|
|
import path from "node:path";
|
|
import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
import { assertNoSymlinkParents, pathScope } from "openclaw/plugin-sdk/security-runtime";
|
|
|
|
export function toRepoPath(filePath: string): string {
|
|
return filePath.split(path.sep).join("/");
|
|
}
|
|
|
|
export function toRepoRelativePath(repoRoot: string, filePath: string): string {
|
|
return toRepoPath(path.relative(repoRoot, filePath));
|
|
}
|
|
|
|
export function isRepoRootRelativeRef(value: string) {
|
|
return !path.isAbsolute(value) && value.split(/[\\/]+/u).every((part) => part !== "..");
|
|
}
|
|
|
|
export function resolveRepoRelativeOutputDir(repoRoot: string, outputDir?: string) {
|
|
if (!outputDir) {
|
|
return undefined;
|
|
}
|
|
if (path.isAbsolute(outputDir)) {
|
|
throw new Error("--output-dir must be a relative path inside the repo root.");
|
|
}
|
|
const resolved = pathScope(repoRoot, { label: "repo root" }).resolve(outputDir);
|
|
if (!resolved.ok) {
|
|
throw new Error("--output-dir must stay within the repo root.");
|
|
}
|
|
return resolved.path;
|
|
}
|
|
|
|
function assertRepoRelativePath(repoRoot: string, targetPath: string, label: string) {
|
|
if (!isPathInside(repoRoot, targetPath)) {
|
|
throw new Error(`${label} must stay within the repo root.`);
|
|
}
|
|
return path.relative(repoRoot, targetPath);
|
|
}
|
|
|
|
async function assertNoSymlinkSegments(repoRoot: string, targetPath: string, label: string) {
|
|
assertRepoRelativePath(repoRoot, targetPath, label);
|
|
try {
|
|
await assertNoSymlinkParents({
|
|
rootDir: repoRoot,
|
|
targetPath,
|
|
messagePrefix: label,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message.includes("symlink")) {
|
|
throw new Error(`${label} must not traverse symlinks.`, { cause: error });
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function ensureRepoBoundDirectory(
|
|
repoRoot: string,
|
|
targetDir: string,
|
|
label: string,
|
|
opts?: { mode?: number },
|
|
) {
|
|
await assertNoSymlinkSegments(path.resolve(repoRoot), path.resolve(targetDir), label);
|
|
const result = await pathScope(repoRoot, { label }).ensureDir(targetDir, { mode: opts?.mode });
|
|
if (!result.ok) {
|
|
throw new Error(`${label} must stay within the repo root.`);
|
|
}
|
|
return result.path;
|
|
}
|