Archived
fix(plugin-api): federate workspace file mutations and trim PR scope
This commit is contained in:
@@ -10,7 +10,3 @@ dist/
|
||||
|
||||
# Local runtime attachment uploads (created by the chat composer "save to folder" mode).
|
||||
.pi-web/
|
||||
|
||||
# Local paste upload directory and temporary working docs.
|
||||
.pi-paste/
|
||||
docs/tmp/
|
||||
|
||||
+2
-2
@@ -976,13 +976,13 @@ If you are an AI agent building or editing a PI WEB plugin, follow this checklis
|
||||
Check discovery:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8504/pi-web-plugins/manifest.json
|
||||
curl http://127.0.0.1:8504/pi-web-plugins/manifest.json
|
||||
```
|
||||
|
||||
Check a plugin module:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8504/pi-web-plugins/my-plugin/pi-web-plugin.js
|
||||
curl http://127.0.0.1:8504/pi-web-plugins/my-plugin/pi-web-plugin.js
|
||||
```
|
||||
|
||||
Common issues:
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
"prepublishOnly": "npm run verify",
|
||||
"publish:npm": "npm publish --access public",
|
||||
"prepare": "node scripts/install-git-hooks.mjs",
|
||||
"postinstall": "node scripts/postinstall.mjs",
|
||||
"changeset": "changeset",
|
||||
"release:version": "changeset version",
|
||||
"changelog:status": "changeset status"
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { chmodSync, readdirSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
/**
|
||||
* node-pty 1.1.0 ships macOS prebuilds with `spawn-helper` files at 644
|
||||
* instead of 755, causing `posix_spawnp failed` at runtime. This script
|
||||
* fixes permissions after install on Darwin platforms.
|
||||
*/
|
||||
function fixNodePtyPermissions() {
|
||||
if (process.platform === "win32") return;
|
||||
const prebuildsDir = join("node_modules", "node-pty", "prebuilds");
|
||||
let entries;
|
||||
try {
|
||||
entries = readdirSync(prebuildsDir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const helper = join(prebuildsDir, entry.name, "spawn-helper");
|
||||
let stats;
|
||||
try {
|
||||
stats = statSync(helper);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (!stats.isFile()) continue;
|
||||
// 0o100 = regular file, 0o111 = owner/group/other execute
|
||||
if ((stats.mode & 0o111) === 0) {
|
||||
chmodSync(helper, 0o755);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fixNodePtyPermissions();
|
||||
@@ -37,6 +37,9 @@ describe("federated route contract", () => {
|
||||
ignoreParseFailure(workspacesApi.deleteWorkspace("p 1", "w 1", machineId)),
|
||||
ignoreParseFailure(workspacesApi.workspaceTree("p 1", "w 1", "src", machineId)),
|
||||
ignoreParseFailure(workspacesApi.workspaceFile("p 1", "w 1", "README.md", machineId)),
|
||||
ignoreParseFailure(workspacesApi.writeWorkspaceFile("p 1", "w 1", "README.md", "hello", { overwrite: false }, machineId)),
|
||||
ignoreParseFailure(workspacesApi.deleteWorkspaceFile("p 1", "w 1", "README.md", machineId)),
|
||||
ignoreParseFailure(workspacesApi.moveWorkspaceFile("p 1", "w 1", "README.md", "docs/README.md", { overwrite: false }, machineId)),
|
||||
ignoreParseFailure(filesApi.files("/repo", "README", { kind: "tracked", mode: "file", machineId })),
|
||||
ignoreParseFailure(gitApi.gitStatus("p 1", "w 1", machineId)),
|
||||
ignoreParseFailure(gitApi.gitDiff("p 1", "w 1", { path: "README.md", staged: true }, machineId)),
|
||||
|
||||
@@ -22,7 +22,7 @@ describe("PiWebPluginService", () => {
|
||||
files: { "pi-web-plugin.js": "export default { apiVersion: 1, name: 'Info', activate: () => ({ contributions: {} }) };" },
|
||||
});
|
||||
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false });
|
||||
|
||||
await expect(service.manifest()).resolves.toEqual({
|
||||
plugins: [expect.objectContaining({ id: "info", source: "test", scope: "local", machineSpecific: false })],
|
||||
@@ -41,7 +41,7 @@ describe("PiWebPluginService", () => {
|
||||
files: { "pi-web-plugin.js": "export default {};" },
|
||||
});
|
||||
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false });
|
||||
|
||||
await expect(service.manifest()).resolves.toMatchObject({ plugins: [{ id: "updates", machineSpecific: true }] });
|
||||
await expect(service.plugins()).resolves.toMatchObject({ plugins: [{ id: "updates", machineSpecific: true, enabled: true }] });
|
||||
@@ -74,7 +74,7 @@ describe("PiWebPluginService", () => {
|
||||
files: { "dist/pi-web-plugin.js": "export default { apiVersion: 1, name: 'Source Dev', activate: () => ({ contributions: {} }) };" },
|
||||
});
|
||||
|
||||
const service = new PiWebPluginService({ cwd: tempDir, packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ cwd: tempDir, packageProvider: false });
|
||||
|
||||
const manifest = await service.manifest();
|
||||
expect(manifest.plugins).toEqual(expect.arrayContaining([
|
||||
@@ -92,7 +92,7 @@ describe("PiWebPluginService", () => {
|
||||
await mkdir(join(tempDir, "plugins"), { recursive: true });
|
||||
await symlink(pluginDir, join(tempDir, "plugins", "dev"), process.platform === "win32" ? "junction" : "dir");
|
||||
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false });
|
||||
|
||||
const manifest = await service.manifest();
|
||||
expect(manifest.plugins).toHaveLength(1);
|
||||
@@ -135,7 +135,7 @@ describe("PiWebPluginService", () => {
|
||||
files: { "pi-web-plugin.js": "export default {};" },
|
||||
});
|
||||
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false });
|
||||
|
||||
const manifest = await service.manifest();
|
||||
expect(manifest.plugins.map((plugin) => plugin.id)).toEqual(["duplicate"]);
|
||||
@@ -167,7 +167,7 @@ describe("PiWebPluginService", () => {
|
||||
files: { "pi-web-plugin.js": "export default {};" },
|
||||
});
|
||||
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false });
|
||||
|
||||
const manifest = await service.manifest();
|
||||
expect(manifest.plugins.map((plugin) => plugin.id)).toEqual(["valid"]);
|
||||
@@ -181,7 +181,7 @@ describe("PiWebPluginService", () => {
|
||||
});
|
||||
await writeFile(join(tempDir, "plugins", "escape.js"), "nope");
|
||||
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false, configProvider: () => ({ plugins: {} }) });
|
||||
const service = new PiWebPluginService({ roots: [{ path: join(tempDir, "plugins"), source: "test", scope: "local" }], packageProvider: false });
|
||||
|
||||
const manifest = await service.manifest();
|
||||
expect(manifest.plugins).toHaveLength(1);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type FederatedHttpMethod = "GET" | "POST" | "DELETE";
|
||||
export type FederatedHttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
||||
|
||||
export interface FederatedHttpRouteSpec {
|
||||
method: FederatedHttpMethod;
|
||||
@@ -15,6 +15,9 @@ export const FEDERATED_HTTP_ROUTES = [
|
||||
{ method: "DELETE", path: "/projects/:projectId/workspaces/:workspaceId" },
|
||||
{ method: "GET", path: "/projects/:projectId/workspaces/:workspaceId/tree" },
|
||||
{ method: "GET", path: "/projects/:projectId/workspaces/:workspaceId/file" },
|
||||
{ method: "PUT", path: "/projects/:projectId/workspaces/:workspaceId/file" },
|
||||
{ method: "DELETE", path: "/projects/:projectId/workspaces/:workspaceId/file" },
|
||||
{ method: "POST", path: "/projects/:projectId/workspaces/:workspaceId/file/move" },
|
||||
{ method: "GET", path: "/projects/:projectId/workspaces/:workspaceId/file/preview" },
|
||||
{ method: "GET", path: "/projects/:projectId/workspaces/:workspaceId/git/status" },
|
||||
{ method: "GET", path: "/projects/:projectId/workspaces/:workspaceId/git/diff" },
|
||||
|
||||
Reference in New Issue
Block a user