


























@@ -117,10 +117,6 @@ async function packToArchive({
117117return dest;
118118}
119119120-function readVoiceCallArchiveBuffer(version: string): Buffer {
121-return fs.readFileSync(path.join(pluginFixturesDir, `voice-call-${version}.tgz`));
122-}
123-124120function getArchiveFixturePath(params: {
125121cacheKey: string;
126122outName: string;
@@ -140,8 +136,6 @@ function readZipperArchiveBuffer(): Buffer {
140136return fs.readFileSync(path.join(pluginFixturesDir, "zipper-0.0.1.zip"));
141137}
142138143-const VOICE_CALL_ARCHIVE_V1_BUFFER = readVoiceCallArchiveBuffer("0.0.1");
144-const VOICE_CALL_ARCHIVE_V2_BUFFER = readVoiceCallArchiveBuffer("0.0.2");
145139const ZIPPER_ARCHIVE_BUFFER = readZipperArchiveBuffer();
146140147141function expectPluginFiles(result: { targetDir: string }, stateDir: string, pluginId: string) {
@@ -430,13 +424,17 @@ async function installArchivePackageAndReturnResult(params: {
430424outName: string;
431425withDistIndex?: boolean;
432426flatRoot?: boolean;
427+writePluginManifest?: boolean;
428+manifestId?: string;
433429}) {
434430const stateDir = suiteTempRootTracker.makeTempDir();
435431const archivePath = await ensureDynamicArchiveTemplate({
436432outName: params.outName,
437433packageJson: params.packageJson,
438434withDistIndex: params.withDistIndex === true,
439435flatRoot: params.flatRoot === true,
436+writePluginManifest: params.writePluginManifest,
437+manifestId: params.manifestId,
440438});
441439442440const extensionsDir = path.join(stateDir, "extensions");
@@ -452,12 +450,16 @@ function buildDynamicArchiveTemplateKey(params: {
452450withDistIndex: boolean;
453451distIndexJsContent?: string;
454452flatRoot: boolean;
453+writePluginManifest?: boolean;
454+manifestId?: string;
455455}): string {
456456return JSON.stringify({
457457packageJson: params.packageJson,
458458withDistIndex: params.withDistIndex,
459459distIndexJsContent: params.distIndexJsContent ?? null,
460460flatRoot: params.flatRoot,
461+writePluginManifest: params.writePluginManifest ?? true,
462+manifestId: params.manifestId ?? null,
461463});
462464}
463465@@ -467,12 +469,16 @@ async function ensureDynamicArchiveTemplate(params: {
467469withDistIndex: boolean;
468470distIndexJsContent?: string;
469471flatRoot?: boolean;
472+writePluginManifest?: boolean;
473+manifestId?: string;
470474}): Promise<string> {
471475const templateKey = buildDynamicArchiveTemplateKey({
472476packageJson: params.packageJson,
473477withDistIndex: params.withDistIndex,
474478distIndexJsContent: params.distIndexJsContent,
475479flatRoot: params.flatRoot === true,
480+writePluginManifest: params.writePluginManifest,
481+manifestId: params.manifestId,
476482});
477483const cachedPath = dynamicArchiveTemplatePathCache.get(templateKey);
478484if (cachedPath) {
@@ -490,6 +496,18 @@ async function ensureDynamicArchiveTemplate(params: {
490496);
491497}
492498fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify(params.packageJson), "utf-8");
499+if (params.writePluginManifest !== false) {
500+const packageName =
501+typeof params.packageJson.name === "string" ? params.packageJson.name : "fixture-plugin";
502+fs.writeFileSync(
503+path.join(pkgDir, "openclaw.plugin.json"),
504+JSON.stringify({
505+id: params.manifestId ?? packageName,
506+configSchema: { type: "object", properties: {} },
507+}),
508+"utf-8",
509+);
510+}
493511const archivePath = await packToArchive({
494512 pkgDir,
495513outDir: ensureSuiteFixtureRoot(),
@@ -578,15 +596,23 @@ beforeEach(() => {
578596describe("installPluginFromArchive", () => {
579597it("installs scoped archives, rejects duplicate installs, and allows updates", async () => {
580598const stateDir = suiteTempRootTracker.makeTempDir();
581-const archiveV1 = getArchiveFixturePath({
582-cacheKey: "voice-call:0.0.1",
599+const archiveV1 = await ensureDynamicArchiveTemplate({
583600outName: "voice-call-0.0.1.tgz",
584-buffer: VOICE_CALL_ARCHIVE_V1_BUFFER,
601+packageJson: {
602+name: "@openclaw/voice-call",
603+version: "0.0.1",
604+openclaw: { extensions: ["./dist/index.js"] },
605+},
606+withDistIndex: true,
585607});
586-const archiveV2 = getArchiveFixturePath({
587-cacheKey: "voice-call:0.0.2",
608+const archiveV2 = await ensureDynamicArchiveTemplate({
588609outName: "voice-call-0.0.2.tgz",
589-buffer: VOICE_CALL_ARCHIVE_V2_BUFFER,
610+packageJson: {
611+name: "@openclaw/voice-call",
612+version: "0.0.2",
613+openclaw: { extensions: ["./dist/index.js"] },
614+},
615+withDistIndex: true,
590616});
591617592618const extensionsDir = path.join(stateDir, "extensions");
@@ -620,7 +646,7 @@ describe("installPluginFromArchive", () => {
620646expect(manifest.version).toBe("0.0.2");
621647});
622648623-it("installs from a zip archive", async () => {
649+it("rejects native plugin zip archives without openclaw.plugin.json", async () => {
624650const stateDir = suiteTempRootTracker.makeTempDir();
625651const archivePath = getArchiveFixturePath({
626652cacheKey: "zipper:0.0.1",
@@ -633,7 +659,12 @@ describe("installPluginFromArchive", () => {
633659 archivePath,
634660 extensionsDir,
635661});
636-expectSuccessfulArchiveInstall({ result, stateDir, pluginId: "@openclaw/zipper" });
662+expect(result.ok).toBe(false);
663+if (!result.ok) {
664+expect(result.error).toContain("package missing valid openclaw.plugin.json");
665+expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.MISSING_PLUGIN_MANIFEST);
666+}
667+expect(fs.existsSync(resolvePluginInstallDir("@openclaw/zipper", extensionsDir))).toBe(false);
637668});
638669639670it("allows archive installs with dangerous code patterns when forced unsafe install is set", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。