Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -3164,8 +3164,8 @@ identify the platform validation still needed.
#### E2E-022C: Check, pack, install round-trip

- **Preconditions**: A scaffolded plugin directory.
- **Steps**: 1) `pnpm pi-plugin check <dir>`. 2) Delete the file named by `main` and run `check` again. 3) Restore it, declare `contributes.skills` without `agent.prompt.inject`, and run `check` again. 4) `pnpm pi-plugin pack <dir>`. 5) Install the resulting `.piplug` from the plugins page. 6) Ask the agent to run `PluginCheck` and `PluginPack` on the same directory.
- **Expected**: A scaffolded plugin checks clean and reports its file count and size; the missing `main` is an error that blocks `pack`; the inert-skills case is a warning that does not block; `pack` writes `dist/<id>-<version>.piplug` with store-only entries and prints its sha256; the package installs through the normal permission review and appears under Active; the agent tools produce the same verdicts and refuse any directory outside the session workspace.
- **Steps**: 1) `pnpm pi-plugin check <dir>`. 2) Delete the file named by `main` and run `check` again; repeat with the file named by `manifest.renderer` in a plugin that declares one. 3) Restore it, declare `contributes.skills` without `agent.prompt.inject`, and run `check` again. 4) `pnpm pi-plugin pack <dir>`. 5) Install the resulting `.piplug` from the plugins page. 6) Ask the agent to run `PluginCheck` and `PluginPack` on the same directory.
- **Expected**: A scaffolded plugin checks clean and reports its file count and size; the missing `main` or `manifest.renderer` entry is an error that blocks `pack`; the inert-skills case is a warning that does not block; `pack` writes `dist/<id>-<version>.piplug` with store-only entries and prints its sha256; the package installs through the normal permission review and appears under Active; the agent tools produce the same verdicts and refuse any directory outside the session workspace.
- **Specs linked**: `07-plugins/10-plugin-devex.md` §5–§6, `07-plugins/06-plugin-packaging.md`, ADR 0039
- **Acceptance**: G (local packaging round-trip)
- **Milestone**: Post-MVP
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/07-plugins/10-plugin-devex.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ See [15-plugin-center.md](15-plugin-center.md).

`check` reproduces every rule the installer enforces, so `check` passing implies
install will pass. It reports errors — a missing or unparseable `manifest.json`,
missing `main` / `ui.panel` / skill files, a skill path escaping the plugin
missing `main` / `ui.panel` / `manifest.renderer` / skill files, a skill path escaping the plugin
directory, an unknown permission, a symlink, more than 2000 files, more than
50 MB — and warnings, which do not block: high-risk permissions (every
permission [13-plugin-permissions-matrix.md](13-plugin-permissions-matrix.md)
Expand Down
4 changes: 2 additions & 2 deletions docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1406,8 +1406,8 @@ task-candidate E2E 从请求工作树运行,但使用主工作区已经准备
#### E2E-022C:检查、打包、安装往返

- **先决条件**:脚手架插件目录。
- **步骤**:1) `pnpm pi-plugin check <dir>`。 2) 删除 `main` 命名的文件并再次运行 `check`。 3) 恢复它,声明 `contributes.skills` 而不声明 `agent.prompt.inject`,然后再次运行 `check`。 4) `pnpm pi-plugin pack <dir>`。 5) 从插件页面安装生成的 `.piplug`。 6) 要求代理在同一目录上运行 `PluginCheck` 和 `PluginPack`。
- **预期**:脚手架插件检查干净并报告其文件计数和大小;缺少 `main` 是一个阻止 `pack` 的错误; inert-skills 情况是一个不会阻止的警告; `pack` 使用仅存储条目写入 `dist/<id>-<version>.piplug` 并打印其 sha256;该软件包通过正常的权限审查进行安装,并显示在“活动”下;代理工具会产生相同的判断并拒绝会话工作区之外的任何目录。
- **步骤**:1) `pnpm pi-plugin check <dir>`。 2) 删除 `main` 命名的文件并再次运行 `check`;对声明了 `manifest.renderer` 的插件,删除它命名的文件后重复一次。 3) 恢复它,声明 `contributes.skills` 而不声明 `agent.prompt.inject`,然后再次运行 `check`。 4) `pnpm pi-plugin pack <dir>`。 5) 从插件页面安装生成的 `.piplug`。 6) 要求代理在同一目录上运行 `PluginCheck` 和 `PluginPack`。
- **预期**:脚手架插件检查干净并报告其文件计数和大小;缺少 `main` 或 `manifest.renderer` 入口是一个阻止 `pack` 的错误; inert-skills 情况是一个不会阻止的警告; `pack` 使用仅存储条目写入 `dist/<id>-<version>.piplug` 并打印其 sha256;该软件包通过正常的权限审查进行安装,并显示在“活动”下;代理工具会产生相同的判断并拒绝会话工作区之外的任何目录。
- **链接规格**:`07-plugins/10-plugin-devex.md` §5–§6、`07-plugins/06-plugin-packaging.md`、ADR 0039
- **承兑**:G(本地包装往返)
- **里程碑**:后 MVP
Expand Down
2 changes: 1 addition & 1 deletion docs/zh-CN/spec/07-plugins/10-plugin-devex.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pnpm pi-plugin publish .

`check` 重现安装程序强制执行的每条规则,因此 `check` 传递意味着
安装会通过。它报告错误 - 丢失或无法解析的 `manifest.json`,
缺少 `main` / `ui.panel` / 技能文件,转义插件的技能路径
缺少 `main` / `ui.panel` / `manifest.renderer` / 技能文件,转义插件的技能路径
目录,未知权限,符号链接,超过 2000 个文件,超过
50 MB — 以及警告,不会阻止:高风险权限([13-plugin-permissions-matrix.md](13-plugin-permissions-matrix.md)
中标为 high 的全部权限,devkit 测试保证两份列表一致)、权限
Expand Down
31 changes: 31 additions & 0 deletions packages/plugin-devkit/src/check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,37 @@ describe("check", () => {
expect(result.errors.map((e) => e.code)).toContain("manifest.invalid-id");
});

it("fails a manifest.renderer entry that does not exist, as the installer does (#1464)", async () => {
const dir = join(await tempDir(), "renderer-entry");
await scaffold({ dir, template: "panel-basic" });
await editManifest(dir, (m) => {
m.renderer = "renderer/index.mjs";
m.permissions = [...(m.permissions ?? []), "renderer.extension"];
});
const missing = await check(dir);
expect(missing.ok).toBe(false);
const error = missing.errors.find((e) => e.code === "renderer.missing");
expect(error?.message).toContain("renderer/index.mjs");

// A directory with a module name is not an entry either.
await mkdir(join(dir, "renderer/index.mjs"), { recursive: true });
expect((await check(dir)).errors.map((e) => e.code)).toContain("renderer.missing");

await rm(join(dir, "renderer"), { recursive: true });
await mkdir(join(dir, "renderer"), { recursive: true });
await writeFile(join(dir, "renderer/index.mjs"), "export function activate() {}\n", "utf8");
const present = await check(dir);
expect(present.errors.map((e) => e.code)).not.toContain("renderer.missing");

// An entry outside the package is already refused by the manifest validator.
await editManifest(dir, (m) => {
m.renderer = "../renderer/index.mjs";
});
const escaping = await check(dir);
expect(escaping.ok).toBe(false);
expect(escaping.errors.map((e) => e.code)).toEqual(["manifest.invalid"]);
});

it("treats background audio and websocket access as high risk", () => {
for (const permission of [
"net.fetch",
Expand Down
10 changes: 10 additions & 0 deletions packages/plugin-devkit/src/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ export async function check(dirInput: string): Promise<CheckResult> {
errors.push({ code: "panel.missing", message: `ui.panel "${panel}" does not exist` });
}

// validateManifest has already refused an absolute or ".." spelling; the
// installer also refuses an entry that is not a file in the package.
const renderer = manifest.renderer;
if (renderer && !(await fileExists(join(dir, renderer)))) {
errors.push({
code: "renderer.missing",
message: `manifest.renderer "${renderer}" does not exist`,
});
}

for (const view of manifest.contributes?.views ?? []) {
const entry = view?.entry;
if (typeof entry !== "string" || !entry.trim()) continue;
Expand Down
Loading