mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 22:17:00 +00:00
fix(cli): tolerate removed cwd in plugin authoring output (#106425)
This commit is contained in:
@@ -3,13 +3,15 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { Type } from "typebox";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { defineToolPlugin, getToolPluginMetadata } from "../plugin-sdk/tool-plugin.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import {
|
||||
buildToolPluginManifest,
|
||||
buildToolPluginPackageManifest,
|
||||
loadToolPlugin,
|
||||
runPluginsBuildCommand,
|
||||
runPluginsInitCommand,
|
||||
validateToolPluginProject,
|
||||
} from "./plugins-authoring-command.js";
|
||||
@@ -314,6 +316,68 @@ describe("plugin authoring commands", () => {
|
||||
expect(loaded.metadata.tools.map((tool) => tool.name)).toEqual(["source_echo"]);
|
||||
});
|
||||
|
||||
it("finishes a build from an absolute root after the launch directory is removed", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-deleted-cwd-build-"));
|
||||
const packagePath = path.join(tmpDir, "package.json");
|
||||
const entryPath = writeSourceToolPluginProject({
|
||||
tmpDir,
|
||||
packageName: "openclaw-plugin-deleted-cwd-build",
|
||||
pluginId: "deleted-cwd-build",
|
||||
toolName: "deleted_cwd_echo",
|
||||
});
|
||||
const originalCwd = process.cwd();
|
||||
const originalWriteFileSync = fs.writeFileSync.bind(fs);
|
||||
let cwdRemoved = false;
|
||||
const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {});
|
||||
const cwd = vi.spyOn(process, "cwd").mockImplementation(() => {
|
||||
if (cwdRemoved) {
|
||||
throw new Error("ENOENT: no such file or directory, uv_cwd");
|
||||
}
|
||||
return originalCwd;
|
||||
});
|
||||
const writeFileSync = vi
|
||||
.spyOn(fs, "writeFileSync")
|
||||
.mockImplementation((file, data, options) => {
|
||||
originalWriteFileSync(file, data, options);
|
||||
if (file === packagePath) {
|
||||
cwdRemoved = true;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await runPluginsBuildCommand({ root: tmpDir, entry: entryPath });
|
||||
|
||||
expect(fs.existsSync(path.join(tmpDir, "openclaw.plugin.json"))).toBe(true);
|
||||
expect(log).toHaveBeenCalledWith(`Wrote ${path.join(tmpDir, "openclaw.plugin.json")}`);
|
||||
expect(log).toHaveBeenCalledWith(`Updated ${packagePath}`);
|
||||
} finally {
|
||||
writeFileSync.mockRestore();
|
||||
cwd.mockRestore();
|
||||
log.mockRestore();
|
||||
fs.rmSync(tmpDir, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("finishes init with an absolute directory after the launch directory is removed", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-deleted-cwd-init-"));
|
||||
const projectDir = path.join(tmpDir, "demo");
|
||||
const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {});
|
||||
const cwd = vi.spyOn(process, "cwd").mockImplementation(() => {
|
||||
throw new Error("ENOENT: no such file or directory, uv_cwd");
|
||||
});
|
||||
|
||||
try {
|
||||
await runPluginsInitCommand("demo", { directory: projectDir });
|
||||
|
||||
expect(fs.existsSync(path.join(projectDir, "package.json"))).toBe(true);
|
||||
expect(log).toHaveBeenCalledWith(`Created ${projectDir}`);
|
||||
} finally {
|
||||
cwd.mockRestore();
|
||||
log.mockRestore();
|
||||
fs.rmSync(tmpDir, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("scaffolds a dist-entry tool plugin project", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-init-"));
|
||||
const projectDir = path.join(tmpDir, "stock-quotes");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { uniqueStrings } from "@openclaw/normalization-core/string-normalization";
|
||||
import { tryProcessCwd } from "../infra/safe-cwd.js";
|
||||
import { getToolPluginMetadata, type ToolPluginMetadata } from "../plugin-sdk/tool-plugin.js";
|
||||
import {
|
||||
loadPluginManifest,
|
||||
@@ -62,6 +63,13 @@ function writeJsonFile(filePath: string, value: unknown): void {
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
// A removed launch directory must not turn completed authoring work into an
|
||||
// uv_cwd failure while rendering the final path.
|
||||
function formatOutputPath(targetPath: string, fallback: string): string {
|
||||
const cwd = tryProcessCwd();
|
||||
return cwd ? path.relative(cwd, targetPath) || fallback : targetPath;
|
||||
}
|
||||
|
||||
function jsStringLiteral(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
@@ -307,10 +315,8 @@ export async function runPluginsBuildCommand(opts: PluginsBuildOptions): Promise
|
||||
|
||||
writeJsonFile(manifestPath, manifest);
|
||||
writeJsonFile(packagePath, nextPackageManifest);
|
||||
defaultRuntime.log(
|
||||
`Wrote ${path.relative(process.cwd(), manifestPath) || PLUGIN_MANIFEST_FILENAME}`,
|
||||
);
|
||||
defaultRuntime.log(`Updated ${path.relative(process.cwd(), packagePath) || "package.json"}`);
|
||||
defaultRuntime.log(`Wrote ${formatOutputPath(manifestPath, PLUGIN_MANIFEST_FILENAME)}`);
|
||||
defaultRuntime.log(`Updated ${formatOutputPath(packagePath, "package.json")}`);
|
||||
}
|
||||
|
||||
export async function runPluginsValidateCommand(opts: PluginsValidateOptions): Promise<void> {
|
||||
@@ -808,5 +814,5 @@ export async function runPluginsInitCommand(
|
||||
}
|
||||
writeJsonFile(path.join(rootDir, "tsconfig.json"), tsconfig);
|
||||
writeScaffoldVitestConfig(rootDir);
|
||||
defaultRuntime.log(`Created ${path.relative(process.cwd(), rootDir) || "."}`);
|
||||
defaultRuntime.log(`Created ${formatOutputPath(rootDir, ".")}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user