mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-18 00:23:25 -06:00
98b1e8b69c
* fix(plugin-sdk): make the API baseline hash path-free and surface-reachable Manifest hashes now cover only surface-reachable, path-independent facts. Rewrite closure from whole-file/path-keyed traversal to declaration-granular reachability with fail-toward-recall fallbacks and taint-tracked cycles. Exclude source paths from the committed hash and split declaration printing into api-baseline-declaration-print.ts. * fix(plugin-sdk): close side-effect and namespace-import gaps in baseline closure Address ClawSweeper findings: - Traverse side-effect imports before collecting globals. - Normalize repo-owned namespace import types. * refactor(plugin-sdk): rebase precision closure onto the JSONL contract
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
// API baseline normalization removes machine-local path differences from compiler output.
|
|
import path from "node:path";
|
|
|
|
/** Normalize compiler source paths into stable repo-relative or node_modules-relative paths. */
|
|
export function normalizePluginSdkApiSourcePath(repoRoot: string, filePath: string): string {
|
|
const resolvedPath = path.resolve(filePath);
|
|
const relative = path.relative(repoRoot, resolvedPath);
|
|
const relativePosix = relative.split(path.sep).join(path.posix.sep);
|
|
if (
|
|
!relative.startsWith("..") &&
|
|
!path.isAbsolute(relative) &&
|
|
!relativePosix.startsWith("node_modules/")
|
|
) {
|
|
return relativePosix;
|
|
}
|
|
|
|
const pathParts = resolvedPath.split(/[\\/]+/);
|
|
const nodeModulesIndex = pathParts.lastIndexOf("node_modules");
|
|
if (nodeModulesIndex >= 0 && nodeModulesIndex < pathParts.length - 1) {
|
|
return ["node_modules", ...pathParts.slice(nodeModulesIndex + 1)].join(path.posix.sep);
|
|
}
|
|
|
|
return relativePosix;
|
|
}
|
|
|
|
function normalizeDeclarationImportSpecifier(repoRoot: string, value: string): string {
|
|
if (!path.isAbsolute(value) && !/^[A-Za-z]:[\\/]/.test(value)) {
|
|
return value;
|
|
}
|
|
|
|
const resolvedPath = path.resolve(value);
|
|
const relative = path.relative(repoRoot, resolvedPath);
|
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
return value;
|
|
}
|
|
return relative.split(path.sep).join(path.posix.sep);
|
|
}
|
|
|
|
function isRepoOwnedImportSpecifier(value: string): boolean {
|
|
return (
|
|
value.startsWith("./") ||
|
|
value.startsWith("../") ||
|
|
/^(?:apps|extensions|packages|scripts|src|test|ui)\//u.test(value)
|
|
);
|
|
}
|
|
|
|
/** Strip machine-local absolute paths from declaration text before hashing baseline output. */
|
|
export function normalizePluginSdkApiDeclarationText(repoRoot: string, value: string): string {
|
|
const repoOwnedSpecifiers = new Set<string>();
|
|
const normalized = value.replaceAll(
|
|
/import\("([^"]+)"((?:\s*,[^)]*)?)\)/g,
|
|
(match, specifier: string, suffix: string) => {
|
|
const normalizedSpecifier = normalizeDeclarationImportSpecifier(repoRoot, specifier);
|
|
if (normalizedSpecifier !== specifier || isRepoOwnedImportSpecifier(normalizedSpecifier)) {
|
|
repoOwnedSpecifiers.add(normalizedSpecifier);
|
|
}
|
|
return normalizedSpecifier === specifier
|
|
? match
|
|
: `import("${normalizedSpecifier}"${suffix})`;
|
|
},
|
|
);
|
|
const withoutRepoQualifiers = normalized.replaceAll(
|
|
/import\("([^"]+)"((?:\s*,[^)]*)?)\)((?:\.[A-Za-z_$][\w$]*)+)/g,
|
|
(match, specifier: string, _suffix: string, qualifier: string) =>
|
|
repoOwnedSpecifiers.has(specifier) ? qualifier.slice(1) : match,
|
|
);
|
|
return withoutRepoQualifiers.replaceAll(
|
|
/import\("([^"]+)"((?:\s*,[^)]*)?)\)/g,
|
|
(match, specifier: string, suffix: string) =>
|
|
repoOwnedSpecifiers.has(specifier) ? `import("<repo>"${suffix})` : match,
|
|
);
|
|
}
|