refactor(core): dedupe command, hook, and cron fixtures

This commit is contained in:
Peter Steinberger
2026-03-02 21:30:58 +00:00
parent 5f0cbd0edc
commit 91dd89313a
16 changed files with 325 additions and 330 deletions

View File

@@ -3,11 +3,15 @@ import path from "node:path";
import { MANIFEST_KEY } from "../compat/legacy-names.js";
import { fileExists, readJsonFile, resolveArchiveKind } from "../infra/archive.js";
import { resolveExistingInstallPath, withExtractedArchiveRoot } from "../infra/install-flow.js";
import { installFromValidatedNpmSpecArchive } from "../infra/install-from-npm-spec.js";
import {
resolveInstallModeOptions,
resolveTimedInstallModeOptions,
} from "../infra/install-mode-options.js";
import { installPackageDir } from "../infra/install-package-dir.js";
import {
installPackageDir,
installPackageDirWithManifestDeps,
} from "../infra/install-package-dir.js";
import { resolveSafeInstallDir, unscopedPackageName } from "../infra/install-safe-path.js";
import {
type NpmIntegrityDrift,
@@ -18,11 +22,6 @@ import {
ensureInstallTargetAvailable,
resolveCanonicalInstallTarget,
} from "../infra/install-target.js";
import {
finalizeNpmSpecArchiveInstall,
installFromNpmSpecArchiveWithInstaller,
} from "../infra/npm-pack-install.js";
import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
import { isPathInside, isPathInsideWithRealpath } from "../security/scan-paths.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { parseFrontmatter } from "./frontmatter.js";
@@ -231,17 +230,15 @@ async function installHookPackageFromDir(params: {
};
}
const deps = manifest.dependencies ?? {};
const hasDeps = Object.keys(deps).length > 0;
const installRes = await installPackageDir({
const installRes = await installPackageDirWithManifestDeps({
sourceDir: params.packageDir,
targetDir,
mode,
timeoutMs,
logger,
copyErrorPrefix: "failed to copy hook pack",
hasDeps,
depsLogMessage: "Installing hook pack dependencies…",
manifestDependencies: manifest.dependencies,
});
if (!installRes.ok) {
return installRes;
@@ -376,14 +373,10 @@ export async function installHooksFromNpmSpec(params: {
}): Promise<InstallHooksResult> {
const { logger, timeoutMs, mode, dryRun } = resolveTimedInstallModeOptions(params, defaultLogger);
const expectedHookPackId = params.expectedHookPackId;
const spec = params.spec.trim();
const specError = validateRegistryNpmSpec(spec);
if (specError) {
return { ok: false, error: specError };
}
const spec = params.spec;
logger.info?.(`Downloading ${spec}`);
const flowResult = await installFromNpmSpecArchiveWithInstaller({
logger.info?.(`Downloading ${spec.trim()}`);
return await installFromValidatedNpmSpecArchive({
tempDirPrefix: "openclaw-hook-pack-",
spec,
timeoutMs,
@@ -402,7 +395,6 @@ export async function installHooksFromNpmSpec(params: {
expectedHookPackId,
},
});
return finalizeNpmSpecArchiveInstall(flowResult);
}
export async function installHooksFromPath(params: {

View File

@@ -5,6 +5,22 @@ import { describe, expect, it } from "vitest";
import { MANIFEST_KEY } from "../compat/legacy-names.js";
import { loadHookEntriesFromDir } from "./workspace.js";
function writeHookPackageManifest(pkgDir: string, hooks: string[]): void {
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks,
},
},
null,
2,
),
);
}
describe("hooks workspace", () => {
it("ignores package.json hook paths that traverse outside package directory", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hooks-workspace-"));
@@ -19,19 +35,7 @@ describe("hooks workspace", () => {
fs.writeFileSync(path.join(outsideHookDir, "HOOK.md"), "---\nname: outside\n---\n");
fs.writeFileSync(path.join(outsideHookDir, "handler.js"), "export default async () => {};\n");
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks: ["../outside"],
},
},
null,
2,
),
);
writeHookPackageManifest(pkgDir, ["../outside"]);
const entries = loadHookEntriesFromDir({ dir: hooksRoot, source: "openclaw-workspace" });
expect(entries.some((e) => e.hook.name === "outside")).toBe(false);
@@ -49,19 +53,7 @@ describe("hooks workspace", () => {
fs.writeFileSync(path.join(nested, "HOOK.md"), "---\nname: nested\n---\n");
fs.writeFileSync(path.join(nested, "handler.js"), "export default async () => {};\n");
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks: ["./nested"],
},
},
null,
2,
),
);
writeHookPackageManifest(pkgDir, ["./nested"]);
const entries = loadHookEntriesFromDir({ dir: hooksRoot, source: "openclaw-workspace" });
expect(entries.some((e) => e.hook.name === "nested")).toBe(true);
@@ -85,19 +77,7 @@ describe("hooks workspace", () => {
return;
}
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks: ["./linked"],
},
},
null,
2,
),
);
writeHookPackageManifest(pkgDir, ["./linked"]);
const entries = loadHookEntriesFromDir({ dir: hooksRoot, source: "openclaw-workspace" });
expect(entries.some((e) => e.hook.name === "outside")).toBe(false);