mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 03:00:34 +00:00
fix: bound auto-discovered resources to roots
This commit is contained in:
@@ -120,6 +120,50 @@ describe("DefaultPackageManager", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps auto-discovered project resources inside their resource roots", async () => {
|
||||
const root = await makeTempDir("openclaw-package-manager-");
|
||||
const configRoot = join(root, ".openclaw");
|
||||
const outsideRoot = join(root, "outside");
|
||||
const insidePrompt = join(configRoot, "prompts", "inside.md");
|
||||
const insideTheme = join(configRoot, "themes", "inside.json");
|
||||
const insideExtension = join(configRoot, "extensions", "inside.ts");
|
||||
await mkdir(join(root, ".git"));
|
||||
await mkdir(join(configRoot, "prompts"), { recursive: true });
|
||||
await mkdir(join(configRoot, "themes"), { recursive: true });
|
||||
await mkdir(join(configRoot, "extensions"), { recursive: true });
|
||||
await mkdir(outsideRoot, { recursive: true });
|
||||
await writeFile(insidePrompt, "# Inside\n", "utf-8");
|
||||
await writeFile(insideTheme, "{}\n", "utf-8");
|
||||
await writeFile(insideExtension, "export default {};\n", "utf-8");
|
||||
await writeFile(join(outsideRoot, "outside.md"), "# Outside\n", "utf-8");
|
||||
await writeFile(join(outsideRoot, "outside.json"), "{}\n", "utf-8");
|
||||
await writeFile(join(outsideRoot, "outside.ts"), "export default {};\n", "utf-8");
|
||||
|
||||
try {
|
||||
await symlink(join(outsideRoot, "outside.md"), join(configRoot, "prompts", "linked.md"));
|
||||
await symlink(join(outsideRoot, "outside.json"), join(configRoot, "themes", "linked.json"));
|
||||
await symlink(join(outsideRoot, "outside.ts"), join(configRoot, "extensions", "linked.ts"));
|
||||
await symlink(outsideRoot, join(configRoot, "extensions", "linked-dir"), "dir");
|
||||
} catch {
|
||||
// Some filesystems disallow symlinks; the inside assertions still prove discovery.
|
||||
}
|
||||
|
||||
const manager = new DefaultPackageManager({
|
||||
cwd: root,
|
||||
agentDir: join(root, "agent"),
|
||||
settingsManager: SettingsManager.inMemory({}),
|
||||
});
|
||||
|
||||
const resolved = await manager.resolve();
|
||||
|
||||
expect(resolved.prompts.map((prompt) => prompt.path)).toContain(insidePrompt);
|
||||
expect(resolved.themes.map((theme) => theme.path)).toContain(insideTheme);
|
||||
expect(resolved.extensions.map((extension) => extension.path)).toContain(insideExtension);
|
||||
expect(resolved.prompts.some((prompt) => prompt.path.includes("linked"))).toBe(false);
|
||||
expect(resolved.themes.some((theme) => theme.path.includes("linked"))).toBe(false);
|
||||
expect(resolved.extensions.some((extension) => extension.path.includes("linked"))).toBe(false);
|
||||
});
|
||||
|
||||
it("does not auto-install missing npm package resources", async () => {
|
||||
const root = await makeTempDir("openclaw-package-manager-");
|
||||
const manager = new DefaultPackageManager({
|
||||
|
||||
@@ -422,6 +422,9 @@ function collectAutoPromptEntries(dir: string): string[] {
|
||||
}
|
||||
|
||||
const fullPath = join(dir, entry.name);
|
||||
if (!isRealPathWithinRoot(dir, fullPath)) {
|
||||
continue;
|
||||
}
|
||||
let isFile = entry.isFile();
|
||||
if (entry.isSymbolicLink()) {
|
||||
try {
|
||||
@@ -467,6 +470,9 @@ function collectAutoThemeEntries(dir: string): string[] {
|
||||
}
|
||||
|
||||
const fullPath = join(dir, entry.name);
|
||||
if (!isRealPathWithinRoot(dir, fullPath)) {
|
||||
continue;
|
||||
}
|
||||
let isFile = entry.isFile();
|
||||
if (entry.isSymbolicLink()) {
|
||||
try {
|
||||
@@ -502,7 +508,7 @@ function readResourceManifestFile(packageJsonPath: string): ResourceManifest | n
|
||||
}
|
||||
}
|
||||
|
||||
function resolveExtensionEntries(dir: string): string[] | null {
|
||||
function resolveExtensionEntries(dir: string, rootDir = dir): string[] | null {
|
||||
const packageJsonPath = join(dir, "package.json");
|
||||
if (existsSync(packageJsonPath)) {
|
||||
const manifest = readResourceManifestFile(packageJsonPath);
|
||||
@@ -510,7 +516,7 @@ function resolveExtensionEntries(dir: string): string[] | null {
|
||||
const entries: string[] = [];
|
||||
for (const extPath of manifest.extensions) {
|
||||
const resolvedExtPath = resolve(dir, extPath);
|
||||
if (existsSync(resolvedExtPath)) {
|
||||
if (existsSync(resolvedExtPath) && isRealPathWithinRoot(rootDir, resolvedExtPath)) {
|
||||
entries.push(resolvedExtPath);
|
||||
}
|
||||
}
|
||||
@@ -522,10 +528,10 @@ function resolveExtensionEntries(dir: string): string[] | null {
|
||||
|
||||
const indexTs = join(dir, "index.ts");
|
||||
const indexJs = join(dir, "index.js");
|
||||
if (existsSync(indexTs)) {
|
||||
if (existsSync(indexTs) && isRealPathWithinRoot(rootDir, indexTs)) {
|
||||
return [indexTs];
|
||||
}
|
||||
if (existsSync(indexJs)) {
|
||||
if (existsSync(indexJs) && isRealPathWithinRoot(rootDir, indexJs)) {
|
||||
return [indexJs];
|
||||
}
|
||||
|
||||
@@ -559,6 +565,9 @@ function collectAutoExtensionEntries(dir: string): string[] {
|
||||
}
|
||||
|
||||
const fullPath = join(dir, entry.name);
|
||||
if (!isRealPathWithinRoot(dir, fullPath)) {
|
||||
continue;
|
||||
}
|
||||
let isDir = entry.isDirectory();
|
||||
let isFile = entry.isFile();
|
||||
|
||||
@@ -581,7 +590,7 @@ function collectAutoExtensionEntries(dir: string): string[] {
|
||||
if (isFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
|
||||
entries.push(fullPath);
|
||||
} else if (isDir) {
|
||||
const resolvedEntries = resolveExtensionEntries(fullPath);
|
||||
const resolvedEntries = resolveExtensionEntries(fullPath, dir);
|
||||
if (resolvedEntries) {
|
||||
entries.push(...resolvedEntries);
|
||||
}
|
||||
@@ -622,7 +631,10 @@ function isPathWithinRoot(root: string, candidate: string): boolean {
|
||||
}
|
||||
|
||||
function isRealPathWithinRoot(root: string, candidate: string): boolean {
|
||||
return isPathWithinRoot(resolveRealPathIfPossible(resolve(root)), resolveRealPathIfPossible(candidate));
|
||||
return isPathWithinRoot(
|
||||
resolveRealPathIfPossible(resolve(root)),
|
||||
resolveRealPathIfPossible(candidate),
|
||||
);
|
||||
}
|
||||
|
||||
function matchesAnyPattern(filePath: string, patterns: string[], baseDir: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user