Merge remote-tracking branch 'public-gh/master' into paperclip-subissues
* public-gh/master: Drop lockfile from watcher change Tighten plugin dev file watching Fix plugin smoke example typecheck Fix plugin dev watcher and migration snapshot Clarify plugin authoring and external dev workflow Expand kitchen sink plugin demos fix: set AGENT_HOME env var for agent processes Add kitchen sink plugin example Simplify plugin runtime and cleanup lifecycle Add plugin framework and settings UI # Conflicts: # packages/db/src/migrations/meta/0029_snapshot.json # packages/db/src/migrations/meta/_journal.json
This commit is contained in:
68
server/src/__tests__/plugin-dev-watcher.test.ts
Normal file
68
server/src/__tests__/plugin-dev-watcher.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { resolvePluginWatchTargets } from "../services/plugin-dev-watcher.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
while (tempDirs.length > 0) {
|
||||
const dir = tempDirs.pop();
|
||||
if (dir) rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function makeTempPluginDir(): string {
|
||||
const dir = mkdtempSync(path.join(os.tmpdir(), "paperclip-plugin-watch-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
describe("resolvePluginWatchTargets", () => {
|
||||
it("watches package metadata plus concrete declared runtime files", () => {
|
||||
const pluginDir = makeTempPluginDir();
|
||||
mkdirSync(path.join(pluginDir, "dist", "ui"), { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@acme/example",
|
||||
paperclipPlugin: {
|
||||
manifest: "./dist/manifest.js",
|
||||
worker: "./dist/worker.js",
|
||||
ui: "./dist/ui",
|
||||
},
|
||||
}),
|
||||
);
|
||||
writeFileSync(path.join(pluginDir, "dist", "manifest.js"), "export default {};\n");
|
||||
writeFileSync(path.join(pluginDir, "dist", "worker.js"), "export default {};\n");
|
||||
writeFileSync(path.join(pluginDir, "dist", "ui", "index.js"), "export default {};\n");
|
||||
writeFileSync(path.join(pluginDir, "dist", "ui", "index.css"), "body {}\n");
|
||||
|
||||
const targets = resolvePluginWatchTargets(pluginDir);
|
||||
|
||||
expect(targets).toEqual([
|
||||
{ path: path.join(pluginDir, "dist", "manifest.js"), recursive: false, kind: "file" },
|
||||
{ path: path.join(pluginDir, "dist", "ui", "index.css"), recursive: false, kind: "file" },
|
||||
{ path: path.join(pluginDir, "dist", "ui", "index.js"), recursive: false, kind: "file" },
|
||||
{ path: path.join(pluginDir, "dist", "worker.js"), recursive: false, kind: "file" },
|
||||
{ path: path.join(pluginDir, "package.json"), recursive: false, kind: "file" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to dist when package metadata does not declare entrypoints", () => {
|
||||
const pluginDir = makeTempPluginDir();
|
||||
mkdirSync(path.join(pluginDir, "dist", "nested"), { recursive: true });
|
||||
writeFileSync(path.join(pluginDir, "package.json"), JSON.stringify({ name: "@acme/example" }));
|
||||
writeFileSync(path.join(pluginDir, "dist", "manifest.js"), "export default {};\n");
|
||||
writeFileSync(path.join(pluginDir, "dist", "nested", "chunk.js"), "export default {};\n");
|
||||
|
||||
const targets = resolvePluginWatchTargets(pluginDir);
|
||||
|
||||
expect(targets).toEqual([
|
||||
{ path: path.join(pluginDir, "package.json"), recursive: false, kind: "file" },
|
||||
{ path: path.join(pluginDir, "dist", "manifest.js"), recursive: false, kind: "file" },
|
||||
{ path: path.join(pluginDir, "dist", "nested", "chunk.js"), recursive: false, kind: "file" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
43
server/src/__tests__/plugin-worker-manager.test.ts
Normal file
43
server/src/__tests__/plugin-worker-manager.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appendStderrExcerpt, formatWorkerFailureMessage } from "../services/plugin-worker-manager.js";
|
||||
|
||||
describe("plugin-worker-manager stderr failure context", () => {
|
||||
it("appends worker stderr context to failure messages", () => {
|
||||
expect(
|
||||
formatWorkerFailureMessage(
|
||||
"Worker process exited (code=1, signal=null)",
|
||||
"TypeError: Unknown file extension \".ts\"",
|
||||
),
|
||||
).toBe(
|
||||
"Worker process exited (code=1, signal=null)\n\nWorker stderr:\nTypeError: Unknown file extension \".ts\"",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not duplicate stderr that is already present", () => {
|
||||
const message = [
|
||||
"Worker process exited (code=1, signal=null)",
|
||||
"",
|
||||
"Worker stderr:",
|
||||
"TypeError: Unknown file extension \".ts\"",
|
||||
].join("\n");
|
||||
|
||||
expect(
|
||||
formatWorkerFailureMessage(message, "TypeError: Unknown file extension \".ts\""),
|
||||
).toBe(message);
|
||||
});
|
||||
|
||||
it("keeps only the latest stderr excerpt", () => {
|
||||
let excerpt = "";
|
||||
excerpt = appendStderrExcerpt(excerpt, "first line");
|
||||
excerpt = appendStderrExcerpt(excerpt, "second line");
|
||||
|
||||
expect(excerpt).toContain("first line");
|
||||
expect(excerpt).toContain("second line");
|
||||
|
||||
excerpt = appendStderrExcerpt(excerpt, "x".repeat(9_000));
|
||||
|
||||
expect(excerpt).not.toContain("first line");
|
||||
expect(excerpt).not.toContain("second line");
|
||||
expect(excerpt.length).toBeLessThanOrEqual(8_000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user