feat(pi): verify bundled coding plugin artifact resources

This commit is contained in:
2026-08-27 18:56:24 +08:00
parent 8fea40238f
commit 7141a91a2c
2 changed files with 301 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
// @vitest-environment node
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import path from 'node:path';
@@ -12,6 +12,7 @@ import {
collectAbsoluteManifestValues,
collectForbiddenAsarPaths,
collectForbiddenResourcePaths,
verifyBundledCodingPluginResources,
defaultProductExecutable,
validatePiArtifactMetadata,
} from '../../scripts/lib/pi-product-artifact.mjs';
@@ -52,6 +53,40 @@ function matchingMetadata() {
};
}
async function bundledResourceFixture() {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-bundled-plugin-'));
roots.push(root);
const projectRoot = path.join(root, 'project');
const sourceResources = path.join(projectRoot, 'resources');
const pluginRoot = path.join(sourceResources, 'coding-plugins', 'example');
const capability = {
pluginId: 'example.plugin',
adapterId: 'example-adapter',
skills: [{ id: 'example', entry: '../skills/example/SKILL.md', grants: [] }],
tools: [{ name: 'example_read' }],
};
await mkdir(path.join(pluginRoot, 'com.makelore'), { recursive: true });
await mkdir(path.join(pluginRoot, 'skills', 'example', 'assets'), { recursive: true });
await mkdir(path.join(sourceResources, 'coding-skills', 'agent-browser'), { recursive: true });
await writeFile(path.join(pluginRoot, 'plugin.json'), JSON.stringify({
name: 'example.plugin',
version: '1.0.0',
extensions: { 'com.makelore': { capabilityManifest: './com.makelore/capability.json' } },
}));
await writeFile(path.join(pluginRoot, 'com.makelore', 'capability.json'), JSON.stringify(capability));
await writeFile(path.join(pluginRoot, 'skills', 'example', 'SKILL.md'), '# Example Skill\n');
await writeFile(path.join(pluginRoot, 'skills', 'example', 'assets', 'sdk.ts'), 'export {};\n');
await writeFile(path.join(pluginRoot, 'skills', 'example', 'assets', 'sdk.js'), 'export {};\n');
await writeFile(path.join(sourceResources, 'coding-skills', 'agent-browser', 'SKILL.md'), '# Browser\n');
const resourcesDirectory = path.join(root, 'packaged');
await cp(sourceResources, path.join(resourcesDirectory, 'resources'), { recursive: true });
return {
projectRoot,
resourcesDirectory,
appAsarContents: Buffer.from('example.plugin example-adapter example_read'),
};
}
describe('final Pi product artifact verification', () => {
it('requires the same pinned Pi version across package, lock, app, runtime, and manifest', () => {
expect(validatePiArtifactMetadata(matchingMetadata())).toMatchObject({
@@ -128,6 +163,49 @@ describe('final Pi product artifact verification', () => {
});
});
it('proves bundled plugin manifests, Skills, SDK assets, catalog, and core resources', async () => {
const fixture = await bundledResourceFixture();
await expect(verifyBundledCodingPluginResources(fixture)).resolves.toMatchObject({
root: 'resources/coding-plugins',
packages: [{
id: 'example.plugin',
manifestPath: 'resources/coding-plugins/example/plugin.json',
capabilityManifestPath: 'resources/coding-plugins/example/com.makelore/capability.json',
skills: [{ id: 'example', path: 'skills/example/SKILL.md' }],
sdkAssets: expect.arrayContaining([
'resources/coding-plugins/example/skills/example/assets/sdk.ts',
'resources/coding-plugins/example/skills/example/assets/sdk.js',
]),
adapterId: 'example-adapter',
tools: ['example_read'],
}],
catalog: { adapters: ['example-adapter'], tools: ['example_read'], packagedInAsar: true },
coreResources: { root: 'resources/coding-skills', skills: ['agent-browser'] },
result: 'pass',
});
});
it('rejects a packaged plugin tree that drops an SDK asset or catalog marker', async () => {
const fixture = await bundledResourceFixture();
await rm(path.join(
fixture.resourcesDirectory,
'resources',
'coding-plugins',
'example',
'skills',
'example',
'assets',
'sdk.js',
));
await expect(verifyBundledCodingPluginResources(fixture)).rejects.toThrow('files differ');
const complete = await bundledResourceFixture();
await expect(verifyBundledCodingPluginResources({
...complete,
appAsarContents: Buffer.from('example.plugin example-adapter'),
})).rejects.toThrow('adapter/tool catalog');
});
it('uses final unpacked-product paths and strictly parses verifier options', () => {
expect(defaultProductExecutable('/repo', 'linux'))
.toBe(path.resolve('/repo', 'release', 'linux-unpacked', 'niancode'));