← Files ACE Carousel CreatorARCHIVED FILE
scripts/package-plugin.mjs
5.46 KB · Nov 3, 2026 · 19:19 UTC
import assert from 'node:assert/strict';
import { lstat, readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { zipSync, unzipSync } from 'fflate';
import { parse } from 'yaml';
const root = fileURLToPath(new URL('../', import.meta.url));
const output = resolve(process.argv[2] ?? fileURLToPath(new URL('../../carousel-creator.zip', import.meta.url)));
const included = [
'plugin.json', 'mcp.json', 'assets/icon.png',
'skills/create-carousel/SKILL.md', 'skills/create-carousel/references/DSL_LAYOUT_SPEC.md',
'package.json', 'package-lock.json', 'scripts/build-carousel.mjs', 'scripts/package-plugin.mjs',
'src/archive.mjs', 'src/document.mjs', 'src/server.mjs',
'examples/creator-demo.yaml', 'examples/creator-demo.carousel', 'examples/creator-demo-preview.png',
'tests/carousel.test.mjs', 'tests/http.integration.mjs',
];
const files = {};
for (const relative of included) {
const path = resolve(root, relative);
assert.ok((await lstat(path)).isFile(), `Expected a regular file: ${relative}`);
files[relative] = await readFile(path);
}
const manifest = JSON.parse(files['plugin.json']);
const metadata = manifest.extensions['com.openai'].interface;
const singleLine = (value, maximum, field) => {
assert.equal(typeof value, 'string', `${field} must be a string`);
assert.ok(value.trim() && [...value].length <= maximum && !/[\r\n\t]/.test(value), `Invalid ${field}`);
};
singleLine(manifest.name, 64, 'name');
assert.match(manifest.name, /^[a-z0-9]+(?:-[a-z0-9]+)*$/);
assert.match(manifest.version, /^\d+\.\d+\.\d+$/);
singleLine(metadata.displayName, 30, 'displayName');
singleLine(metadata.shortDescription, 30, 'shortDescription');
singleLine(metadata.developerName, 80, 'developerName');
assert.ok(metadata.longDescription.trim() && [...metadata.longDescription].length <= 4000);
assert.equal(metadata.category, 'Creativity');
assert.ok(metadata.capabilities.length <= 20);
for (const value of metadata.capabilities) singleLine(value, 120, 'capability');
for (const field of ['websiteURL', 'supportURL', 'privacyPolicyURL', 'termsOfServiceURL']) {
singleLine(metadata[field], 1024, field);
const url = new URL(metadata[field]);
assert.equal(url.protocol, 'https:');
assert.ok(!url.username && !url.password);
}
assert.ok(metadata.defaultPrompt.length <= 3);
assert.equal(new Set(metadata.defaultPrompt.map(s => s.normalize('NFC').trim())).size, metadata.defaultPrompt.length);
for (const prompt of metadata.defaultPrompt) singleLine(prompt, 128, 'defaultPrompt');
for (const field of ['logo', 'composerIcon']) {
assert.ok(metadata[field].startsWith('./'));
assert.ok(files[metadata[field].slice(2)], `Missing ${field} file`);
}
const icon = files['assets/icon.png'];
assert.equal(icon.subarray(0, 8).toString('hex'), '89504e470d0a1a0a');
assert.equal(icon.readUInt32BE(16), icon.readUInt32BE(20));
assert.ok(icon.readUInt32BE(16) >= 48 && icon.readUInt32BE(16) <= 4096 && icon.length <= 5 * 1024 * 1024);
const mcp = JSON.parse(files['mcp.json']);
assert.equal(mcp.$schema, 'https://agent-plugins.org/schemas/1.0.0/mcp.schema.json');
assert.equal(Object.keys(mcp.mcpServers).length, 1);
assert.deepEqual(mcp.mcpServers['carousel-creator'], { type: 'streamable-http', url: 'https://mcp.quokka.software/mcp' });
const skill = files['skills/create-carousel/SKILL.md'].toString('utf8');
const frontmatter = skill.match(/^---\n([\s\S]*?)\n---\n/);
assert.ok(frontmatter, 'Skill frontmatter missing');
const identity = parse(frontmatter[1]);
assert.equal(identity.name, 'create-carousel');
singleLine(identity.description, 1024, 'skill description');
assert.ok(skill.slice(frontmatter[0].length).trim());
assert.ok(files['skills/create-carousel/references/DSL_LAYOUT_SPEC.md'].length);
const review = manifest.extensions['com.openai'].review;
if (review.demo_recording_url) {
const demoUrl = new URL(review.demo_recording_url);
assert.equal(demoUrl.protocol, 'https:');
assert.ok(!demoUrl.username && !demoUrl.password);
}
assert.equal(review.test_cases.positive.length, 5);
assert.equal(review.test_cases.negative.length, 3);
for (const test of [...review.test_cases.positive, ...review.test_cases.negative]) {
assert.ok(test.description.trim() && test.prompt.trim() && test.expected_behavior.trim());
}
for (const test of review.test_cases.positive) assert.ok(test.tools_triggered.trim());
for (const translation of Object.values(manifest.extensions['com.openai'].publication.translations ?? {})) {
singleLine(translation.subtitle, 30, 'translated subtitle');
assert.ok(translation.description.trim() && [...translation.description].length <= 4000);
}
const bytes = zipSync(files, { level: 9 });
assert.ok(bytes.length < 100_000_000);
const extracted = unzipSync(bytes);
assert.deepEqual(Object.keys(extracted).sort(), included.toSorted());
for (const name of included) assert.deepEqual(Buffer.from(extracted[name]), files[name], `ZIP mismatch: ${name}`);
await writeFile(output, bytes);
console.log(JSON.stringify({ zip: output, bytes: bytes.length, files: included.length, version: manifest.version, display_name: metadata.displayName, mcp_url: mcp.mcpServers['carousel-creator'].url, demo_recording_url: review.demo_recording_url ?? null, positive_cases: 5, negative_cases: 3, pending_before_review: ['Publish hosted-plugin privacy and terms additions', 'Confirm all eight prompts with the installed plugin', ...(!review.demo_recording_url ? ['Provide a real accessible demo recording'] : []), 'Complete portal scans and domain verification'] }, null, 2));
SHA-256: 698df0d5de5c1a076b100f47f0e00705467a85b77d31b9fae15e5bc36ebb7fa8