From d8281ac76602b94ab3a9a19ddd4c16b0090f0ded Mon Sep 17 00:00:00 2001 From: DeWitt Gibson Date: Sat, 4 Apr 2026 12:23:46 -0700 Subject: [PATCH] Add plugin build script and configs Introduce a build system for Agentbase plugins: add scripts/build-plugin.ts to validate manifests, compile per-plugin TypeScript, and package each plugin into packages/plugins/dist/*.zip. Add a packages/plugins/tsconfig.json and new npm scripts (root: build:plugins; packages/plugins: build:plugin, build:plugins). Add runtime/dev deps (adm-zip, ajv, ts-node and types) and update pnpm lockfile. Also resolve/clean up tsconfig conflicts in packages/plugins/template/__tests__/tsconfig.json. --- package.json | 1 + packages/plugins/package.json | 8 +- packages/plugins/scripts/build-plugin.ts | 244 ++++++++++++++++++ .../plugins/template/__tests__/tsconfig.json | 8 - packages/plugins/tsconfig.json | 14 + pnpm-lock.yaml | 53 ++-- 6 files changed, 297 insertions(+), 31 deletions(-) create mode 100644 packages/plugins/scripts/build-plugin.ts create mode 100644 packages/plugins/tsconfig.json diff --git a/package.json b/package.json index ad140ef..b4a80c7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build": "pnpm -r build", "build:core": "pnpm --filter @agentbase/core build", "build:frontend": "pnpm --filter @agentbase/frontend build", + "build:plugins": "pnpm --filter @agentbase/plugin-sdk build:plugins", "test": "pnpm -r test", "test:core": "pnpm --filter @agentbase/core test", "lint": "pnpm -r lint", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index bcad726..66cfba4 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -6,12 +6,18 @@ "types": "sdk/src/index.ts", "license": "GPL-3.0-or-later", "scripts": { - "build": "tsc" + "build": "tsc", + "build:plugin": "ts-node --project tsconfig.json scripts/build-plugin.ts", + "build:plugins": "ts-node --project tsconfig.json scripts/build-plugin.ts --all" }, "devDependencies": { + "@types/adm-zip": "^0.5.8", "@types/jest": "^30.0.0", + "adm-zip": "^0.5.17", + "ajv": "^8.18.0", "jest": "^29.7.0", "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", "typescript": "^5.7.0" } } diff --git a/packages/plugins/scripts/build-plugin.ts b/packages/plugins/scripts/build-plugin.ts new file mode 100644 index 0000000..c347d2f --- /dev/null +++ b/packages/plugins/scripts/build-plugin.ts @@ -0,0 +1,244 @@ +#!/usr/bin/env node +/** + * Build script for Agentbase plugins. + * + * Usage: + * ts-node scripts/build-plugin.ts # build one plugin + * ts-node scripts/build-plugin.ts --all # build every plugin in official/ + * + * For each plugin this script: + * 1. Reads and validates manifest.json against the same Ajv schema used by + * the marketplace scanning service. + * 2. Compiles TypeScript → JS (ES2020 / CommonJS) via a per-plugin tsconfig. + * 3. Packages the compiled output + manifest.json into + * packages/plugins/dist/-.zip + */ + +import * as fs from "node:fs"; +import * as path from "node:path"; +import { execSync } from "node:child_process"; +import Ajv from "ajv"; +import AdmZip from "adm-zip"; + +// ─── Manifest schema (mirrors scanning.service.ts in marketplace) ───────────── + +const MANIFEST_SCHEMA = { + type: "object", + required: ["name", "version", "description", "entryPoint"], + properties: { + name: { type: "string", minLength: 1 }, + version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+" }, + description: { type: "string", minLength: 1 }, + entryPoint: { type: "string", minLength: 1 }, + agentbaseVersion: { type: "string" }, + author: { type: "string" }, + permissions: { type: "array", items: { type: "string" } }, + peerDependencies: { + type: "object", + additionalProperties: { type: "string" }, + }, + }, + additionalProperties: true, +} as const; + +const ajv = new Ajv(); +const validateManifest = ajv.compile(MANIFEST_SCHEMA); + +// ─── Paths ──────────────────────────────────────────────────────────────────── + +const PLUGINS_ROOT = path.resolve(__dirname, ".."); +const OFFICIAL_DIR = path.join(PLUGINS_ROOT, "official"); +const DIST_DIR = path.join(PLUGINS_ROOT, "dist"); + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +interface PluginManifest { + name: string; + version: string; + description: string; + entryPoint: string; + [key: string]: unknown; +} + +function readManifest(pluginDir: string): PluginManifest { + const manifestPath = path.join(pluginDir, "manifest.json"); + if (!fs.existsSync(manifestPath)) { + throw new Error(`manifest.json not found in ${pluginDir}`); + } + const raw = fs.readFileSync(manifestPath, "utf-8"); + return JSON.parse(raw) as PluginManifest; +} + +function validateManifestOrThrow( + manifest: PluginManifest, + pluginDir: string, +): void { + const valid = validateManifest(manifest); + if (!valid) { + const errors = (validateManifest.errors ?? []) + .map( + (e) => + ` - ${e.instancePath || "(root)"}: ${e.message ?? "unknown error"}`, + ) + .join("\n"); + throw new Error( + `manifest.json in ${pluginDir} failed validation:\n${errors}`, + ); + } +} + +function compilePlugin(pluginDir: string, slug: string): void { + // Generate a transient tsconfig that sets correct rootDir/outDir + const tsconfigPath = path.join(pluginDir, "tsconfig.build.json"); + const tsconfig = { + extends: "./tsconfig.json", + compilerOptions: { + target: "ES2020", + module: "commonjs", + outDir: "./dist", + rootDir: "./src", + noEmit: false, + declaration: false, + sourceMap: false, + }, + include: ["src/**/*"], + exclude: ["node_modules", "dist", "__tests__"], + }; + + fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2)); + try { + // Resolve tsc relative to the plugin's own node_modules then fall back to + // the workspace root node_modules so this works in both monorepo and CI. + const tscBin = + path.join(pluginDir, "node_modules", ".bin", "tsc") + + (process.platform === "win32" ? ".cmd" : ""); + const fallbackTsc = + path.join(PLUGINS_ROOT, "..", "..", "node_modules", ".bin", "tsc") + + (process.platform === "win32" ? ".cmd" : ""); + const tsc = fs.existsSync(tscBin) ? tscBin : fallbackTsc; + + console.log(` [tsc] Compiling ${slug}…`); + execSync(`"${tsc}" --project "${tsconfigPath}"`, { + cwd: pluginDir, + stdio: "inherit", + }); + } finally { + // Always remove the transient config regardless of success/failure + fs.rmSync(tsconfigPath, { force: true }); + } +} + +function buildZip(pluginDir: string, slug: string, version: string): string { + const compiledDir = path.join(pluginDir, "dist"); + if (!fs.existsSync(compiledDir)) { + throw new Error( + `Compiled output directory not found at ${compiledDir}. Did tsc succeed?`, + ); + } + + fs.mkdirSync(DIST_DIR, { recursive: true }); + + const zipName = `${slug}-${version}.zip`; + const zipPath = path.join(DIST_DIR, zipName); + + const zip = new AdmZip(); + + // Add manifest.json + zip.addLocalFile(path.join(pluginDir, "manifest.json")); + + // Recursively add all files from dist/ + addDirectoryToZip(zip, compiledDir, ""); + + zip.writeZip(zipPath); + return zipPath; +} + +function addDirectoryToZip(zip: AdmZip, dir: string, zipPrefix: string): void { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + const zipPath = zipPrefix ? `${zipPrefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) { + addDirectoryToZip(zip, fullPath, zipPath); + } else { + zip.addFile(zipPath, fs.readFileSync(fullPath)); + } + } +} + +// ─── Core build function ────────────────────────────────────────────────────── + +function buildPlugin(slug: string): void { + const pluginDir = path.join(OFFICIAL_DIR, slug); + + if (!fs.existsSync(pluginDir)) { + throw new Error(`Plugin directory not found: ${pluginDir}`); + } + + console.log(`\nBuilding plugin: ${slug}`); + + const manifest = readManifest(pluginDir); + validateManifestOrThrow(manifest, pluginDir); + console.log(` [manifest] Valid — ${manifest.name}@${manifest.version}`); + + compilePlugin(pluginDir, slug); + console.log(` [tsc] Compiled successfully`); + + const zipPath = buildZip(pluginDir, slug, manifest.version); + console.log(` [zip] Created: ${path.relative(process.cwd(), zipPath)}`); +} + +function buildAll(): void { + if (!fs.existsSync(OFFICIAL_DIR)) { + console.warn(`No official/ directory found at ${OFFICIAL_DIR}`); + return; + } + + const slugs = fs + .readdirSync(OFFICIAL_DIR, { withFileTypes: true }) + .filter((e) => e.isDirectory()) + .map((e) => e.name); + + if (slugs.length === 0) { + console.warn("No plugins found in official/ — nothing to build."); + return; + } + + const failed: string[] = []; + for (const slug of slugs) { + try { + buildPlugin(slug); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + console.error(` [FAILED] ${slug}: ${message}`); + failed.push(slug); + } + } + + console.log(`\n─────────────────────────────────────────`); + console.log(`Built ${slugs.length - failed.length}/${slugs.length} plugins.`); + if (failed.length > 0) { + console.error(`Failed: ${failed.join(", ")}`); + process.exit(1); + } +} + +// ─── Entry point ────────────────────────────────────────────────────────────── + +const [, , ...args] = process.argv; + +if (args.length === 0) { + console.error("Usage: build-plugin.ts | --all"); + process.exit(1); +} + +if (args[0] === "--all") { + buildAll(); +} else { + const slug = args[0]; + try { + buildPlugin(slug); + } catch (err: unknown) { + console.error(err instanceof Error ? err.message : String(err)); + process.exit(1); + } +} diff --git a/packages/plugins/template/__tests__/tsconfig.json b/packages/plugins/template/__tests__/tsconfig.json index b6e90e8..65fa7c7 100644 --- a/packages/plugins/template/__tests__/tsconfig.json +++ b/packages/plugins/template/__tests__/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.json", "compilerOptions": { "noEmit": true, -<<<<<<< Updated upstream "rootDir": "../../..", "baseUrl": "..", "lib": ["ES2022", "DOM"], @@ -12,12 +11,5 @@ } }, "include": ["**/*.ts", "../src/**/*.ts"], -======= - "rootDir": "../..", - "lib": ["ES2022", "DOM"], - "types": ["jest"] - }, - "include": ["**/*.ts", "../src/**/*.ts", "../../../sdk/src/**/*.ts"], ->>>>>>> Stashed changes "exclude": ["node_modules"] } diff --git a/packages/plugins/tsconfig.json b/packages/plugins/tsconfig.json new file mode 100644 index 0000000..3ff3a39 --- /dev/null +++ b/packages/plugins/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["scripts/**/*"], + "exclude": ["node_modules", "dist", "official", "template", "examples"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 510583b..1bd92c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,11 +178,7 @@ importers: version: 7.2.2 ts-jest: specifier: ^29.2.0 -<<<<<<< Updated upstream - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3) -======= - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@29.7.0)(typescript@5.9.3) ->>>>>>> Stashed changes + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11))(typescript@5.9.3) ts-node: specifier: ^10.9.0 version: 10.9.2(@types/node@22.19.11)(typescript@5.9.3) @@ -288,11 +284,7 @@ importers: version: 1.0.7(tailwindcss@3.4.19) ts-jest: specifier: ^29.4.6 -<<<<<<< Updated upstream - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3) -======= - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@29.7.0)(typescript@5.9.3) ->>>>>>> Stashed changes + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11))(typescript@5.9.3) typescript: specifier: ^5.7.0 version: 5.9.3 @@ -345,15 +337,27 @@ importers: packages/plugins: devDependencies: + '@types/adm-zip': + specifier: ^0.5.8 + version: 0.5.8 '@types/jest': specifier: ^30.0.0 version: 30.0.0 + adm-zip: + specifier: ^0.5.17 + version: 0.5.17 + ajv: + specifier: ^8.18.0 + version: 8.18.0 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)) ts-jest: specifier: ^29.4.6 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11))(typescript@5.9.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.19.11)(typescript@5.9.3) typescript: specifier: ^5.7.0 version: 5.9.3 @@ -372,7 +376,7 @@ importers: version: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)) ts-jest: specifier: ^29.2.0 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@29.7.0)(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11))(typescript@5.9.3) typescript: specifier: ^5.7.0 version: 5.9.3 @@ -393,11 +397,7 @@ importers: version: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)) ts-jest: specifier: ^29.2.0 -<<<<<<< Updated upstream - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3) -======= - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@29.7.0)(typescript@5.9.3) ->>>>>>> Stashed changes + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11))(typescript@5.9.3) typescript: specifier: ^5.3.0 version: 5.9.3 @@ -1657,6 +1657,9 @@ packages: '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/adm-zip@0.5.8': + resolution: {integrity: sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1954,6 +1957,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.17: + resolution: {integrity: sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==} + engines: {node: '>=12.0'} + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -7259,6 +7266,10 @@ snapshots: dependencies: '@types/estree': 1.0.8 + '@types/adm-zip@0.5.8': + dependencies: + '@types/node': 22.19.11 + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -7625,6 +7636,8 @@ snapshots: acorn@8.15.0: {} + adm-zip@0.5.17: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -11774,11 +11787,7 @@ snapshots: ts-interface-checker@0.1.13: {} -<<<<<<< Updated upstream - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3): -======= - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@29.7.0)(typescript@5.9.3): ->>>>>>> Stashed changes + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@30.3.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@29.7.0(@types/node@22.19.11))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0