@kubb/studio 5.2.6 → 5.2.8

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/README.md CHANGED
@@ -96,6 +96,38 @@ nothing can read it back.
96
96
  member can approve. A host that pairs a shared or tier-limited agent passes `clientId: 'kubb-agent'`
97
97
  and an `agentKind`, whose codes only an admin can approve.
98
98
 
99
+ ## Asynchronous jobs
100
+
101
+ CI and automation queue work with `createJob` and poll with `waitForJob`. Both send the
102
+ organization CI API key as `x-api-key`. They do not open a WebSocket.
103
+
104
+ | Step | Call | What it does |
105
+ | ------ | -------------------- | --------------------------------------------------------------------- |
106
+ | Queue | `POST /api/jobs` | Accepts a `generation` or `snapshot` job and returns `202` with an id |
107
+ | Status | `GET /api/jobs/{id}` | Returns the job until `success` or `failed` |
108
+
109
+ ```typescript
110
+ import { createJob, waitForJob } from '@kubb/studio'
111
+
112
+ const job = await createJob({
113
+ studioUrl: 'https://kubb.studio',
114
+ token: process.env.KUBB_TOKEN!,
115
+ type: 'snapshot',
116
+ agentId: agent.id,
117
+ name: '@scope/package',
118
+ version: '1.0.0',
119
+ })
120
+
121
+ const finished = await waitForJob({
122
+ studioUrl: 'https://kubb.studio',
123
+ token: process.env.KUBB_TOKEN!,
124
+ id: job.id,
125
+ })
126
+
127
+ if (finished.status === 'failed') throw new Error(finished.error)
128
+ const snapshot = finished.snapshot
129
+ ```
130
+
99
131
  ## Protocol
100
132
 
101
133
  `@kubb/studio/protocol` holds the WebSocket message types shared by both ends, so the agent and
package/dist/index.cjs CHANGED
@@ -411,14 +411,21 @@ async function loadOrCreateFallbackSecret() {
411
411
  return secret;
412
412
  }
413
413
  /**
414
+ * Hashes a stable secret into the machine token Studio expects, so a host can derive one from its
415
+ * own identity without duplicating `getMachineToken`'s SHA-256 step.
416
+ */
417
+ function machineTokenFrom(secret) {
418
+ return (0, node_crypto.hash)("sha256", secret);
419
+ }
420
+ /**
414
421
  * Returns the machine token derived from the `KUBB_AGENT_SECRET` environment variable.
415
422
  * Falls back to a generated secret persisted in the runtime storage if the env var is not set.
416
423
  * The token is hashed with SHA-256.
417
424
  */
418
425
  async function getMachineToken() {
419
- if (node_process.default.env.KUBB_AGENT_SECRET) return (0, node_crypto.hash)("sha256", node_process.default.env.KUBB_AGENT_SECRET);
426
+ if (node_process.default.env.KUBB_AGENT_SECRET) return machineTokenFrom(node_process.default.env.KUBB_AGENT_SECRET);
420
427
  fallbackSecretPromise ??= loadOrCreateFallbackSecret();
421
- return (0, node_crypto.hash)("sha256", await fallbackSecretPromise);
428
+ return machineTokenFrom(await fallbackSecretPromise);
422
429
  }
423
430
  //#endregion
424
431
  //#region src/api.ts
@@ -569,9 +576,82 @@ async function disconnect({ sessionId, token, studioUrl, slug }) {
569
576
  console.warn((0, node_util.styleText)("yellow", `[${tag}] Failed to notify Studio of disconnection: ${getErrorMessage(error)}`));
570
577
  }
571
578
  }
579
+ /**
580
+ * Queues a generation or snapshot job on Studio (`POST /api/jobs`).
581
+ *
582
+ * Returns as soon as Studio accepts the job (`202`). Poll with {@link waitForJob} until it finishes.
583
+ * Authenticates with the organization CI API key via `x-api-key`.
584
+ *
585
+ * @example Snapshot job
586
+ * ```ts
587
+ * const job = await createJob({
588
+ * studioUrl: 'https://kubb.studio',
589
+ * token: process.env.KUBB_TOKEN!,
590
+ * type: 'snapshot',
591
+ * agentId: agent.id,
592
+ * name: '@kubb/demo',
593
+ * version: '1.0.0',
594
+ * })
595
+ * const finished = await waitForJob({ studioUrl, token, id: job.id })
596
+ * ```
597
+ */
598
+ async function createJob({ studioUrl, token, type, agentId, name, version, config }) {
599
+ const { job } = await (0, ofetch.ofetch)(`${studioUrl}/api/jobs`, {
600
+ method: "POST",
601
+ headers: { "x-api-key": token },
602
+ body: {
603
+ type,
604
+ agentId,
605
+ name,
606
+ version,
607
+ config
608
+ }
609
+ });
610
+ return job;
611
+ }
612
+ /**
613
+ * Polls `GET /api/jobs/{id}` until the job reaches `success` or `failed`.
614
+ *
615
+ * A `failed` job resolves normally. Check `job.status` and `job.error`. Throws only when the
616
+ * deadline passes before Studio finishes.
617
+ */
618
+ async function waitForJob({ studioUrl, token, id, timeoutMs = 6e4 }) {
619
+ const deadline = Date.now() + timeoutMs;
620
+ for (;;) {
621
+ const { job } = await (0, ofetch.ofetch)(`${studioUrl}/api/jobs/${id}`, { headers: { "x-api-key": token } });
622
+ if (job.status === "success" || job.status === "failed") return job;
623
+ if (Date.now() >= deadline) throw new Error("Timed out waiting for the Studio job");
624
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
625
+ }
626
+ }
627
+ /**
628
+ * Creates or reuses a CI agent (`POST /api/agents`), keyed by `(organization, machineToken)`.
629
+ * Authenticates via `x-api-key`. Reusing the same `machineToken` reuses the same agent instead of
630
+ * consuming a new one from the organization's agent limit.
631
+ */
632
+ async function createAgent({ studioUrl, token, name, machineToken }) {
633
+ try {
634
+ return await (0, ofetch.ofetch)(`${studioUrl}/api/agents`, {
635
+ method: "POST",
636
+ headers: { "x-api-key": token },
637
+ body: {
638
+ name,
639
+ machineToken
640
+ }
641
+ });
642
+ } catch (error) {
643
+ if (error instanceof ofetch.FetchError) {
644
+ const upgradeUrl = error.data?.data?.upgradeUrl;
645
+ const detail = responseMessage(error.data) ?? getErrorMessage(error);
646
+ const hint = upgradeUrl ? ` Agent limit reached; upgrade at ${upgradeUrl}.` : "";
647
+ throw new Error(`Failed to create a Kubb Studio agent: ${detail}${hint}`, { cause: error });
648
+ }
649
+ throw error;
650
+ }
651
+ }
572
652
  //#endregion
573
653
  //#region package.json
574
- var version = "5.2.6";
654
+ var version = "5.2.8";
575
655
  //#endregion
576
656
  //#region src/hooks.ts
577
657
  /**
@@ -2449,13 +2529,17 @@ async function pollForPairingToken({ studioUrl = agentDefaults.studioUrl, sessio
2449
2529
  //#endregion
2450
2530
  exports.InvalidAgentTokenError = InvalidAgentTokenError;
2451
2531
  exports.PairingCanceledError = PairingCanceledError;
2532
+ exports.createAgent = createAgent;
2452
2533
  exports.createClient = createClient;
2453
2534
  exports.createFileStorage = createFileStorage;
2535
+ exports.createJob = createJob;
2454
2536
  exports.createJobId = require_protocol.createJobId;
2455
2537
  exports.defaultStudioUrl = defaultStudioUrl;
2538
+ exports.machineTokenFrom = machineTokenFrom;
2456
2539
  exports.pollForPairingToken = pollForPairingToken;
2457
2540
  exports.runConnection = runConnection;
2458
2541
  exports.setStorage = setStorage;
2459
2542
  exports.startPairing = startPairing;
2543
+ exports.waitForJob = waitForJob;
2460
2544
 
2461
2545
  //# sourceMappingURL=index.cjs.map