@capybara-hq/plugin-tools 0.1.0-rc.3 → 0.1.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pack/pack.js CHANGED
@@ -7,10 +7,11 @@ import { dirname, join, posix, relative, resolve } from "node:path";
7
7
  import { pathToFileURL } from "node:url";
8
8
  import { promisify } from "node:util";
9
9
  import { gzip } from "node:zlib";
10
- import { AGENT_TEMPLATE_LIMITS, checkEntryImports, defineExtension, HOST_PROVIDED_PACKAGES, PLUGIN_MANIFEST_FILE, parseAgentTemplate, parseConnectorTemplateFile, parsePluginManifest, SDK_VERSION, staticManifestOf, validatePluginPackageJsonFor, } from "@capybara-hq/plugin-sdk";
10
+ import { AGENT_TEMPLATE_LIMITS, checkEntryImports, defineExtension, HOST_PROVIDED_PACKAGES, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_LIMITS, parseAgentTemplate, parseConnectorTemplateFile, parsePluginManifest, SDK_VERSION, staticManifestOf, validatePluginPackageJsonFor, } from "@capybara-hq/plugin-sdk";
11
11
  import { init as lexerInit, parse as lexerParse } from "es-module-lexer";
12
12
  import { build } from "esbuild";
13
13
  import { pack as tarPack } from "tar-stream";
14
+ import { extensionConfigSchemas, formatConfigSchemaProblem } from "./config-schemas.js";
14
15
  /** The extension entry the packer writes (relative to the package root). */
15
16
  const EXTENSION_ENTRY = "dist/index.mjs";
16
17
  /**
@@ -363,6 +364,61 @@ async function contentFiles(dir, label, m) {
363
364
  agents: agents.length,
364
365
  };
365
366
  }
367
+ /**
368
+ * Bundles `src/index.ts` into `<stage>/package/dist/index.mjs` (esbuild, single-file ESM), checks
369
+ * the bundle's imports, imports it and runs its default export through defineExtension. Returns the
370
+ * bundle, the defined manifest and the bundled inputs (for the notices).
371
+ */
372
+ async function buildAndImport(dir, label, stage) {
373
+ await linkHostPackages(dir, stage);
374
+ const entryOut = join(stage, "package", EXTENSION_ENTRY);
375
+ const result = await build({
376
+ absWorkingDir: dir,
377
+ entryPoints: [join(dir, "src/index.ts")],
378
+ outfile: entryOut,
379
+ bundle: true,
380
+ platform: "node",
381
+ format: "esm",
382
+ target: "node24",
383
+ external: [...HOST_PROVIDED_PACKAGES],
384
+ plugins: [nodeBuiltins],
385
+ banner: { js: BANNER },
386
+ define: DEFINE,
387
+ charset: "utf8",
388
+ logLevel: "silent",
389
+ metafile: true,
390
+ // Notices of bundled dependencies go to THIRD-PARTY-NOTICES.txt (built from the metafile).
391
+ legalComments: "none",
392
+ });
393
+ if (result.errors.length > 0) {
394
+ throw new Error(`${label}: esbuild failed: ${result.errors.map((e) => e.text).join("; ")}`);
395
+ }
396
+ const code = await readFile(entryOut);
397
+ const issues = await importIssues(code.toString("utf8"));
398
+ if (issues.length > 0)
399
+ throw new Error(`${label}: ${issues.join("; ")}`);
400
+ const mod = await importBundle(entryOut);
401
+ if (typeof mod.default !== "object" || mod.default === null) {
402
+ throw new Error(`${label}: the bundle has no default export object`);
403
+ }
404
+ const m = defineExtension(mod.default);
405
+ return { code, m, inputs: Object.keys(result.metafile?.inputs ?? {}) };
406
+ }
407
+ /**
408
+ * The extension a plugin directory's `src/index.ts` defines, bundled and imported the way the
409
+ * packer does (in a `.capybara-pack-*` staging dir under the system temp dir, removed afterwards).
410
+ * For checks that need the module (config forms); throws what packing would throw.
411
+ */
412
+ export async function importPluginExtension(pluginDir) {
413
+ const dir = resolve(pluginDir);
414
+ const stage = await realpath(await mkdtemp(join(tmpdir(), STAGING_PREFIX)));
415
+ try {
416
+ return (await buildAndImport(dir, dir, stage)).m;
417
+ }
418
+ finally {
419
+ await rm(stage, { recursive: true, force: true });
420
+ }
421
+ }
366
422
  /**
367
423
  * Bundles the extension `src/index.ts`:
368
424
  * 1. esbuild bundles it into the single-file ESM `dist/index.mjs` (platform node, target node24),
@@ -371,6 +427,8 @@ async function contentFiles(dir, label, m) {
371
427
  * 2. the bundle's imports are checked (allowlist, no non-literal dynamic import), the bundle is
372
428
  * imported and its default export goes through defineExtension; `staticManifestOf` gives the
373
429
  * static manifest, whose version must equal the package.json version;
430
+ * the config schemas become `extension.configSchemas` (`configJsonSchema`; refused when their
431
+ * hints have problems, {@link extensionConfigSchemas});
374
432
  * 3. `migrations/` (when the manifest declares migrations, which must resolve to the plugin's
375
433
  * `migrations/`), `ui/` (when present) and THIRD-PARTY-NOTICES.txt join the files.
376
434
  *
@@ -379,41 +437,14 @@ async function contentFiles(dir, label, m) {
379
437
  async function bundleExtension(dir, label, pkgVersion) {
380
438
  const stage = await realpath(await mkdtemp(join(tmpdir(), STAGING_PREFIX)));
381
439
  try {
382
- await linkHostPackages(dir, stage);
383
- const entryOut = join(stage, "package", EXTENSION_ENTRY);
384
- const result = await build({
385
- absWorkingDir: dir,
386
- entryPoints: [join(dir, "src/index.ts")],
387
- outfile: entryOut,
388
- bundle: true,
389
- platform: "node",
390
- format: "esm",
391
- target: "node24",
392
- external: [...HOST_PROVIDED_PACKAGES],
393
- plugins: [nodeBuiltins],
394
- banner: { js: BANNER },
395
- define: DEFINE,
396
- charset: "utf8",
397
- logLevel: "silent",
398
- metafile: true,
399
- // Notices of bundled dependencies go to THIRD-PARTY-NOTICES.txt (built from the metafile).
400
- legalComments: "none",
401
- });
402
- if (result.errors.length > 0) {
403
- throw new Error(`${label}: esbuild failed: ${result.errors.map((e) => e.text).join("; ")}`);
404
- }
405
- const code = await readFile(entryOut);
406
- const issues = await importIssues(code.toString("utf8"));
407
- if (issues.length > 0)
408
- throw new Error(`${label}: ${issues.join("; ")}`);
409
- const mod = await importBundle(entryOut);
410
- if (typeof mod.default !== "object" || mod.default === null) {
411
- throw new Error(`${label}: the bundle has no default export object`);
412
- }
413
- const m = defineExtension(mod.default);
440
+ const { code, m, inputs } = await buildAndImport(dir, label, stage);
414
441
  if (m.version !== pkgVersion) {
415
442
  throw new Error(`${label}: manifest version ${m.version} does not match package.json version ${String(pkgVersion)}`);
416
443
  }
444
+ const config = extensionConfigSchemas(m.config);
445
+ if (config.problems.length > 0) {
446
+ throw new Error(`${label}: invalid config UI hints: ${config.problems.map(formatConfigSchemaProblem).join("; ")}`);
447
+ }
417
448
  const files = new Map([[EXTENSION_ENTRY, code]]);
418
449
  const extension = {
419
450
  entry: `./${EXTENSION_ENTRY}`,
@@ -435,7 +466,10 @@ async function bundleExtension(dir, label, pkgVersion) {
435
466
  }
436
467
  extension.ui = "./ui";
437
468
  }
438
- const notices = await thirdPartyNotices(dir, Object.keys(result.metafile?.inputs ?? {}));
469
+ if (config.schemas.deployment !== undefined || config.schemas.org !== undefined) {
470
+ extension.configSchemas = config.schemas;
471
+ }
472
+ const notices = await thirdPartyNotices(dir, inputs);
439
473
  if (notices !== undefined)
440
474
  files.set(NOTICES_FILE, Buffer.from(notices));
441
475
  return { files, extension };
@@ -504,7 +538,15 @@ export async function packPlugin(pluginDir, outDir) {
504
538
  if (pkgIssues.length > 0)
505
539
  throw new Error(`${label}: ${pkgIssues.join("; ")}`);
506
540
  files.set("package.json", Buffer.from(`${JSON.stringify(pkg, null, 2)}\n`));
507
- files.set(PLUGIN_MANIFEST_FILE, Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`));
541
+ // The packed text is pretty-printed while the config schema caps measure compact JSON: the text
542
+ // itself must stay under the reader's limit, or the kernel would refuse the package unread.
543
+ const manifestText = `${JSON.stringify(manifest, null, 2)}\n`;
544
+ if (manifestText.length > PLUGIN_MANIFEST_LIMITS.manifestChars) {
545
+ throw new Error(`${label}: packed ${PLUGIN_MANIFEST_FILE} is ${manifestText.length} characters, over the ` +
546
+ `${PLUGIN_MANIFEST_LIMITS.manifestChars} limit (PLUGIN_MANIFEST_LIMITS.manifestChars); ` +
547
+ "make the config schemas smaller");
548
+ }
549
+ files.set(PLUGIN_MANIFEST_FILE, Buffer.from(manifestText));
508
550
  const tgz = await makeTgz(files);
509
551
  await mkdir(outDir, { recursive: true });
510
552
  const file = resolve(outDir, tgzName(String(srcPkg.name), String(srcPkg.version)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/plugin-tools",
3
- "version": "0.1.0-rc.3",
3
+ "version": "0.1.0-rc.5",
4
4
  "description": "Capybara plugin toolchain: the capybara-plugin CLI to pack, check and release Capybara plugins",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,12 +51,12 @@
51
51
  "esbuild": "0.28.2",
52
52
  "oxc-parser": "^0.154.0",
53
53
  "tar-stream": "3.2.2",
54
- "@capybara-hq/kernel": "^0.1.0-rc.3",
55
- "@capybara-hq/plugin-sdk": "^0.1.0-rc.1"
54
+ "@capybara-hq/kernel": "^0.1.0-rc.5",
55
+ "@capybara-hq/plugin-sdk": "^0.1.0-rc.3"
56
56
  },
57
57
  "devDependencies": {
58
58
  "zod": "^4.6.7",
59
- "@capybara-hq/testing": "0.1.0-rc.3"
59
+ "@capybara-hq/testing": "0.1.0-rc.6"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsc -b"