feat(pi): verify bundled coding plugin artifact resources
This commit is contained in:
@@ -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'));
|
||||
|
||||
Reference in New Issue
Block a user