@camstack/agent 1.0.8 → 1.1.0

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/cli.js CHANGED
@@ -369,6 +369,29 @@ async function applyDeployedBundle(input) {
369
369
  return { addonDir: liveDir };
370
370
  }
371
371
 
372
+ // src/fetch-bundle-from-hub.ts
373
+ var import_node_crypto2 = require("crypto");
374
+ var import_undici = require("undici");
375
+ async function fetchBundleFromHub(opts) {
376
+ const authHeader = { Authorization: `Bearer ${opts.token}` };
377
+ const res = opts.fetchImpl ? await opts.fetchImpl(opts.url, { headers: authHeader }) : await (0, import_undici.fetch)(opts.url, {
378
+ headers: authHeader,
379
+ dispatcher: new import_undici.Agent({ connect: { rejectUnauthorized: false } })
380
+ });
381
+ if (!res.ok) {
382
+ throw new Error(`bundle fetch failed: HTTP ${res.status}`);
383
+ }
384
+ const buffer = Buffer.from(await res.arrayBuffer());
385
+ if (buffer.length !== opts.bytes) {
386
+ throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`);
387
+ }
388
+ const sha = (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
389
+ if (sha !== opts.sha256) {
390
+ throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`);
391
+ }
392
+ return buffer;
393
+ }
394
+
372
395
  // src/agent-service.ts
373
396
  var deploySwapLogger = {
374
397
  info: (msg) => console.log(`[Agent] ${msg}`),
@@ -402,6 +425,28 @@ async function withTimeout(p, ms, fallback) {
402
425
  }
403
426
  }
404
427
  var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
428
+ function resolveDeployAction(seam) {
429
+ return async (params) => {
430
+ const { addonId, source, bundle } = params;
431
+ if (source && (0, import_system.isAddonDeploySource)(source)) {
432
+ if (source.kind === "npm") {
433
+ await seam.installFromNpm(addonId, source.version);
434
+ return { success: true, addonId };
435
+ }
436
+ const buf2 = await seam.fetchBundle(source);
437
+ const { addonDir: addonDir2 } = await seam.applyBundle(buf2);
438
+ seam.onApplied(addonDir2);
439
+ return { success: true, addonId, path: addonDir2 };
440
+ }
441
+ if (bundle === void 0) {
442
+ throw new Error("$agent.deploy: no source descriptor and no legacy bundle provided");
443
+ }
444
+ const buf = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
445
+ const { addonDir } = await seam.applyBundle(buf);
446
+ seam.onApplied(addonDir);
447
+ return { success: true, addonId, path: addonDir };
448
+ };
449
+ }
405
450
  function readHubAddressFromConfig(configPath) {
406
451
  if (!configPath) return null;
407
452
  try {
@@ -574,8 +619,7 @@ function createAgentService(deps) {
574
619
  deploy: {
575
620
  handler: async (ctx) => {
576
621
  const { params } = ctx;
577
- const { addonId, bundle } = params;
578
- const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
622
+ const { addonId, source, bundle } = params;
579
623
  const { execFile } = await import("child_process");
580
624
  const { promisify } = await import("util");
581
625
  const execFileAsync = promisify(execFile);
@@ -593,17 +637,37 @@ function createAgentService(deps) {
593
637
  }
594
638
  }
595
639
  };
596
- const { addonDir } = await applyDeployedBundle({
597
- addonsDir: deps.addonsDir,
598
- addonId,
599
- bundle: bufferData,
600
- extract,
601
- logger: deploySwapLogger
602
- });
603
- for (const declId of readDeployedAddonIds(addonDir)) {
604
- deps.loadedAddons.delete(declId);
605
- }
606
- return { success: true, addonId, path: addonDir };
640
+ const seam = {
641
+ installFromNpm: (pkg, v) => {
642
+ if (!deps.installFromNpm) {
643
+ throw new Error("npm deploy source unsupported on this agent");
644
+ }
645
+ return deps.installFromNpm(pkg, v);
646
+ },
647
+ applyBundle: (buf) => applyDeployedBundle({
648
+ addonsDir: deps.addonsDir,
649
+ addonId,
650
+ bundle: buf,
651
+ extract,
652
+ logger: deploySwapLogger
653
+ }),
654
+ fetchBundle: (s) => fetchBundleFromHub(s),
655
+ // Evict every addon DECLARATION id this package contributes
656
+ // from `loadedAddons` so the follow-up `$agent.reload` actually
657
+ // re-instantiates it. `loadDeployedAddons` skips any addon
658
+ // still present in `loadedAddons`; without this eviction a
659
+ // redeploy would leave the agent pinned to the pre-update
660
+ // version. The `deploy` param `addonId` is the PACKAGE name
661
+ // (used only as the on-disk dir), whereas `loadedAddons` is
662
+ // keyed by the addon DECLARATION id — they differ for scoped
663
+ // packages, so we read the extracted manifest to bridge them.
664
+ onApplied: (addonDir) => {
665
+ for (const declId of readDeployedAddonIds(addonDir)) {
666
+ deps.loadedAddons.delete(declId);
667
+ }
668
+ }
669
+ };
670
+ return resolveDeployAction(seam)({ addonId, source, bundle });
607
671
  }
608
672
  },
609
673
  /**
@@ -1070,7 +1134,10 @@ async function startAgent(configPath) {
1070
1134
  subtree.registerNode(params);
1071
1135
  triggerUpwardRegistration();
1072
1136
  },
1073
- expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0
1137
+ expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0,
1138
+ installFromNpm: async (pkg, version) => {
1139
+ await installer.install(pkg, version);
1140
+ }
1074
1141
  });
1075
1142
  broker.createService(agentServiceSchema);
1076
1143
  const agentTcpPort = (0, import_system3.deriveAgentListenPort)(broker.nodeID);