@kubb/cli 5.6.0 → 5.6.2

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.
Files changed (65) hide show
  1. package/dist/{Telemetry-bGdFBaGt.cjs → Telemetry-CuO35YOL.cjs} +2 -2
  2. package/dist/{Telemetry-bGdFBaGt.cjs.map → Telemetry-CuO35YOL.cjs.map} +1 -1
  3. package/dist/{Telemetry-RBcAjNxj.js → Telemetry-DqT0XYcN.js} +2 -2
  4. package/dist/{Telemetry-RBcAjNxj.js.map → Telemetry-DqT0XYcN.js.map} +1 -1
  5. package/dist/{background-0PMj56FJ.js → background-CEe9CIIk.js} +3 -7
  6. package/dist/background-CEe9CIIk.js.map +1 -0
  7. package/dist/{background-R0T_7Szu.cjs → background-DF0hM01o.cjs} +2 -2
  8. package/dist/background-DF0hM01o.cjs.map +1 -0
  9. package/dist/background-O8hzI5Pg.js +2 -0
  10. package/dist/{commands-BAgGrcvc.js → commands-D4jkosg_.js} +31 -15
  11. package/dist/commands-D4jkosg_.js.map +1 -0
  12. package/dist/{commands-Si-RFAS6.cjs → commands-DRBfcF_2.cjs} +31 -15
  13. package/dist/commands-DRBfcF_2.cjs.map +1 -0
  14. package/dist/{config-ByfJSj3q.cjs → config-B2BlaYOU.cjs} +2 -2
  15. package/dist/{config-ByfJSj3q.cjs.map → config-B2BlaYOU.cjs.map} +1 -1
  16. package/dist/{config-8Xl4yZmw.js → config-CCxOcwWu.js} +3 -3
  17. package/dist/{config-8Xl4yZmw.js.map → config-CCxOcwWu.js.map} +1 -1
  18. package/dist/index.cjs +13 -19
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +13 -19
  21. package/dist/index.js.map +1 -1
  22. package/dist/{output-B6Ondw0g.js → output-CnSY0tCG.js} +12 -22
  23. package/dist/output-CnSY0tCG.js.map +1 -0
  24. package/dist/{output-i3s5c4Xu.cjs → output-gox62nKo.cjs} +11 -21
  25. package/dist/output-gox62nKo.cjs.map +1 -0
  26. package/dist/{run-DQoKTiGk.js → run-BP_Z7A4e.js} +2 -2
  27. package/dist/{run-DQoKTiGk.js.map → run-BP_Z7A4e.js.map} +1 -1
  28. package/dist/{run-D8Wz_17X.cjs → run-BQgoSvvl.cjs} +68 -139
  29. package/dist/run-BQgoSvvl.cjs.map +1 -0
  30. package/dist/{run-Dgph0WYk.js → run-B_0Vp1ZV.js} +73 -132
  31. package/dist/run-B_0Vp1ZV.js.map +1 -0
  32. package/dist/{run-D5AeacqK.js → run-Bcgdg2P-.js} +2 -2
  33. package/dist/{run-D5AeacqK.js.map → run-Bcgdg2P-.js.map} +1 -1
  34. package/dist/{run-CmNDNEjR.js → run-BipBtba_.js} +9 -6
  35. package/dist/run-BipBtba_.js.map +1 -0
  36. package/dist/{run-BkLJ5mXn.cjs → run-BvKVx0qs.cjs} +2 -2
  37. package/dist/{run-BkLJ5mXn.cjs.map → run-BvKVx0qs.cjs.map} +1 -1
  38. package/dist/{run-t8nm7zcT.cjs → run-DaNJBaav.cjs} +9 -6
  39. package/dist/run-DaNJBaav.cjs.map +1 -0
  40. package/dist/{run-CZlkqkDc.js → run-Jx5dFmC2.js} +2 -2
  41. package/dist/{run-CZlkqkDc.js.map → run-Jx5dFmC2.js.map} +1 -1
  42. package/dist/{run-yvuot2N9.cjs → run-pidaXROC.cjs} +2 -2
  43. package/dist/{run-yvuot2N9.cjs.map → run-pidaXROC.cjs.map} +1 -1
  44. package/dist/{run-3I2NPeXG.cjs → run-yHLrxnxW.cjs} +2 -2
  45. package/dist/{run-3I2NPeXG.cjs.map → run-yHLrxnxW.cjs.map} +1 -1
  46. package/dist/{snapshot-D4nKbwkw.cjs → snapshot-BLEmwvP6.cjs} +66 -134
  47. package/dist/snapshot-BLEmwvP6.cjs.map +1 -0
  48. package/dist/{snapshot-CkHXR0Nl.js → snapshot-DVdepLl5.js} +68 -136
  49. package/dist/snapshot-DVdepLl5.js.map +1 -0
  50. package/dist/studioWorker.js +3 -9
  51. package/dist/studioWorker.js.map +1 -1
  52. package/package.json +8 -12
  53. package/dist/background-0PMj56FJ.js.map +0 -1
  54. package/dist/background-BqS01QDb.js +0 -2
  55. package/dist/background-R0T_7Szu.cjs.map +0 -1
  56. package/dist/commands-BAgGrcvc.js.map +0 -1
  57. package/dist/commands-Si-RFAS6.cjs.map +0 -1
  58. package/dist/output-B6Ondw0g.js.map +0 -1
  59. package/dist/output-i3s5c4Xu.cjs.map +0 -1
  60. package/dist/run-CmNDNEjR.js.map +0 -1
  61. package/dist/run-D8Wz_17X.cjs.map +0 -1
  62. package/dist/run-Dgph0WYk.js.map +0 -1
  63. package/dist/run-t8nm7zcT.cjs.map +0 -1
  64. package/dist/snapshot-CkHXR0Nl.js.map +0 -1
  65. package/dist/snapshot-D4nKbwkw.cjs.map +0 -1
@@ -1,15 +1,31 @@
1
1
  const require_agent = require("./agent-DElUuYuy.cjs");
2
- const require_run = require("./run-D8Wz_17X.cjs");
3
- const require_output = require("./output-i3s5c4Xu.cjs");
4
- const require_config = require("./config-ByfJSj3q.cjs");
2
+ const require_run = require("./run-BQgoSvvl.cjs");
3
+ const require_Telemetry = require("./Telemetry-CuO35YOL.cjs");
4
+ const require_output = require("./output-gox62nKo.cjs");
5
+ const require_config = require("./config-B2BlaYOU.cjs");
5
6
  let node_util = require("node:util");
6
- let node_crypto = require("node:crypto");
7
7
  let node_process = require("node:process");
8
8
  node_process = require_agent.__toESM(node_process, 1);
9
+ let node_fs_promises = require("node:fs/promises");
9
10
  let node_path = require("node:path");
10
11
  let _kubb_core = require("@kubb/core");
11
12
  let node_fs = require("node:fs");
12
13
  let _kubb_studio = require("@kubb/studio");
14
+ //#region ../../internals/utils/src/fs.ts
15
+ /**
16
+ * Reads the file at `path` as a UTF-8 string.
17
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const source = await read('./src/Pet.ts')
22
+ * ```
23
+ */
24
+ async function read(path) {
25
+ if (require_Telemetry.runtime.isBun) return Bun.file(path).text();
26
+ return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
27
+ }
28
+ //#endregion
13
29
  //#region src/runners/studio/ci.ts
14
30
  function readGithubPullRequest(eventPath) {
15
31
  if (!eventPath) return;
@@ -82,35 +98,17 @@ function detectCi(env = node_process.default.env) {
82
98
  //#endregion
83
99
  //#region src/runners/studio/snapshot.ts
84
100
  /**
85
- * How long to wait for Studio's `studio:ready` acknowledgement, above the client's own 10s
86
- * handshake-ack timeout.
101
+ * Walks up from `directory` to the first package.json with a name and a version.
87
102
  */
88
- const READY_TIMEOUT_MS = 15e3;
89
- /**
90
- * Longest `--timeout` accepted. Job records expire from Studio's storage after an hour.
91
- */
92
- const MAX_TIMEOUT_SECONDS = 3600;
93
- const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
94
- "localhost",
95
- "127.0.0.1",
96
- "::1"
97
- ]);
98
- async function findPackageJson(startDirectory) {
99
- let directory = startDirectory;
100
- for (;;) {
101
- const file = (0, node_path.join)(directory, "package.json");
102
- if (await require_run.exists(file)) {
103
- const packageJson = JSON.parse(await require_run.read(file));
104
- if (packageJson.name && packageJson.version) return {
105
- name: packageJson.name,
106
- version: packageJson.version
107
- };
108
- }
109
- const parent = (0, node_path.dirname)(directory);
110
- if (parent === directory) break;
111
- directory = parent;
112
- }
113
- throw new Error(`No package.json with a name and a version found above ${startDirectory}`);
103
+ async function findPackageJson(directory) {
104
+ const packageJson = JSON.parse(await read((0, node_path.join)(directory, "package.json")).catch(() => "{}"));
105
+ if (packageJson.name && packageJson.version) return {
106
+ name: packageJson.name,
107
+ version: packageJson.version
108
+ };
109
+ const parent = (0, node_path.dirname)(directory);
110
+ if (parent === directory) throw new Error(`No package.json with a name and a version found above ${node_process.default.cwd()}`);
111
+ return findPackageJson(parent);
114
112
  }
115
113
  /** Reads package.json only for whichever of name/version --name and --version did not supply. */
116
114
  async function resolvePackageMetadata(options) {
@@ -118,10 +116,10 @@ async function resolvePackageMetadata(options) {
118
116
  name: options.name,
119
117
  version: options.packageVersion
120
118
  };
121
- const packageMetadata = await findPackageJson(node_process.default.cwd());
119
+ const found = await findPackageJson(node_process.default.cwd());
122
120
  return {
123
- name: options.name ?? packageMetadata.name,
124
- version: options.packageVersion ?? packageMetadata.version
121
+ name: options.name ?? found.name,
122
+ version: options.packageVersion ?? found.version
125
123
  };
126
124
  }
127
125
  function resolveToken(options) {
@@ -146,21 +144,10 @@ function resolveCiIdentity(options) {
146
144
  base: base?.id === ci.id ? void 0 : base
147
145
  };
148
146
  }
149
- /** Rejects a bad `--timeout` up front instead of letting it reach `waitForJob` as NaN or <= 0. */
150
- function resolveTimeoutMs(options) {
151
- const seconds = options.timeout ?? 600;
152
- if (!Number.isFinite(seconds) || seconds <= 0) throw new Error("--timeout must be a positive number of seconds");
153
- return Math.min(seconds, MAX_TIMEOUT_SECONDS) * 1e3;
154
- }
155
- /** The CI key is a credential, so refuse to send it anywhere but HTTPS or a loopback dev host. */
156
- function assertSecureStudioUrl(studioUrl) {
157
- const url = new URL(studioUrl);
158
- if (url.protocol !== "https:" && !LOOPBACK_HOSTS.has(url.hostname)) throw new Error(`Refusing to send the CI API key to ${studioUrl}. Use an https:// Studio URL, or a loopback host for local development.`);
159
- }
160
147
  function absoluteUrl(studioUrl, path) {
161
148
  return new URL(path, `${studioUrl}/`).toString();
162
149
  }
163
- function toResult(studioUrl, snapshot, agentSlug, ci) {
150
+ function toResult({ studioUrl, snapshot, ci }) {
164
151
  return {
165
152
  id: snapshot.id,
166
153
  name: snapshot.name,
@@ -169,7 +156,7 @@ function toResult(studioUrl, snapshot, agentSlug, ci) {
169
156
  url: absoluteUrl(studioUrl, snapshot.url),
170
157
  snapshotIdUrl: absoluteUrl(studioUrl, snapshot.snapshotIdUrl),
171
158
  expiresAt: snapshot.expiresAt,
172
- agentUrl: absoluteUrl(studioUrl, `/agents/${agentSlug}`),
159
+ agentUrl: absoluteUrl(studioUrl, `/agents/${snapshot.agent.slug ?? snapshot.agent.id}`),
173
160
  changes: snapshot.changes,
174
161
  branchChanges: snapshot.branchChanges && ci.base ? {
175
162
  ...snapshot.branchChanges,
@@ -203,126 +190,71 @@ function printSummary(result) {
203
190
  ]);
204
191
  }
205
192
  /**
206
- * Generates a Kubb Studio snapshot from a script: registers or reuses a CI agent, connects it,
207
- * queues a snapshot job, and polls until the tarball is ready. A snapshot job runs generation and
208
- * packs the tarball in one step, so this needs no separate generation run.
193
+ * Generates the project, packs it, and publishes it to Kubb Studio in one request, under the CI
194
+ * agent the run's identity names.
209
195
  */
210
196
  async function snapshot(options) {
211
- const timeoutMs = resolveTimeoutMs(options);
212
197
  const token = resolveToken(options);
213
- assertSecureStudioUrl(options.studioUrl);
214
- const { configPath } = await require_run.loadConfigs(options);
198
+ const { config } = await require_run.loadConfigs(options);
215
199
  const ci = resolveCiIdentity(options);
216
200
  const { name, version: packageVersion } = await resolvePackageMetadata(options);
217
201
  const logLevel = _kubb_core.logLevel[options.logLevel ?? "info"];
218
- node_process.default.env.KUBB_AGENT_SECRET = ci.id;
219
202
  const write = options.json ? (line) => console.error(line) : (line) => console.log(line);
220
- const logger = require_output.createPlainLogger(write);
203
+ const hooks = new _kubb_core.Hookable();
204
+ require_config.setupReporters(hooks, {
205
+ logLevel,
206
+ reporters: [_kubb_core.cliReporter],
207
+ logger: require_output.createPlainLogger(write)
208
+ });
221
209
  const step = (message) => {
222
210
  if (logLevel > _kubb_core.logLevel.silent) write(message);
223
211
  };
224
- step("Creating Kubb Studio agent");
225
- const agent = await (0, _kubb_studio.createAgent)({
226
- studioUrl: options.studioUrl,
227
- token,
228
- name: ci.name,
229
- machineToken: (0, _kubb_studio.machineTokenFrom)(ci.id)
230
- });
231
- const instanceId = (0, node_crypto.randomUUID)();
232
- const { promise: ready, resolve: markReady } = Promise.withResolvers();
233
- const shutdown = new AbortController();
234
- const connection = (0, _kubb_studio.runConnection)({
235
- credentials: { token: agent.token },
236
- signal: shutdown.signal,
237
- clientOptions: () => ({
238
- studioUrl: options.studioUrl,
239
- instanceId,
240
- configPath,
241
- root: node_process.default.cwd(),
242
- version: options.version,
243
- permissions: options.permission,
244
- loadConfig: async () => (await require_run.loadConfigs(options)).config,
245
- installLogger: async (hooks) => {
246
- require_config.setupReporters(hooks, {
247
- logLevel,
248
- reporters: [_kubb_core.cliReporter],
249
- logger
250
- });
251
- hooks.hook("studio:ready", () => markReady());
252
- }
253
- }),
254
- onTokenRejected: ({ error }) => Promise.reject(error)
255
- });
256
- const connectionLost = new AbortController();
257
- const connectionEnded = /* @__PURE__ */ new Error("The Kubb Studio connection ended before the snapshot finished");
258
- connection.then(() => connectionLost.abort(connectionEnded), (error) => connectionLost.abort(error));
259
- const lost = connection.then(() => Promise.reject(connectionEnded), (error) => Promise.reject(error));
260
- lost.catch(() => {});
212
+ step("Generating the snapshot");
261
213
  try {
262
- let readyTimeout;
263
- try {
264
- await Promise.race([
265
- ready,
266
- lost,
267
- new Promise((_, reject) => {
268
- readyTimeout = setTimeout(() => reject(/* @__PURE__ */ new Error("Timed out waiting for Kubb Studio to confirm the agent was ready")), READY_TIMEOUT_MS);
269
- })
270
- ]);
271
- } finally {
272
- clearTimeout(readyTimeout);
273
- }
274
- step("Creating snapshot job");
275
- const snapshotDeadline = Date.now() + timeoutMs;
276
- const snapshotTimeoutMs = () => Math.max(snapshotDeadline - Date.now(), 1);
277
- const job = await (0, _kubb_studio.createJob)({
214
+ const published = await (0, _kubb_studio.createSnapshot)({
278
215
  studioUrl: options.studioUrl,
279
- token,
280
- type: "snapshot",
281
- agentId: agent.id,
282
- instanceId,
283
- name,
216
+ apiKey: token,
217
+ config,
218
+ hooks,
219
+ id: ci.id,
220
+ name: ci.name,
221
+ packageName: name,
284
222
  version: packageVersion,
285
223
  commit: ci.commit,
286
- baseId: ci.base?.id,
287
- timeoutMs: snapshotTimeoutMs(),
288
- signal: connectionLost.signal
224
+ baseId: ci.base?.id
289
225
  });
290
- step(`Snapshot job queued: ${job.id}`);
291
- const finished = await Promise.race([(0, _kubb_studio.waitForJob)({
226
+ const result = toResult({
292
227
  studioUrl: options.studioUrl,
293
- token,
294
- id: job.id,
295
- timeoutMs: snapshotTimeoutMs(),
296
- signal: connectionLost.signal
297
- }), lost]);
298
- if (finished.status === "failed") throw new Error(finished.error ?? "Snapshot job failed");
299
- if (!finished.snapshot) throw new Error("Snapshot job succeeded without a snapshot");
300
- const result = toResult(options.studioUrl, finished.snapshot, agent.slug, ci);
228
+ snapshot: published,
229
+ ci
230
+ });
301
231
  step("Snapshot published");
302
232
  if (options.json) console.log(JSON.stringify(result));
303
233
  if (!options.json) printSummary(result);
304
234
  } catch (error) {
305
235
  step("Snapshot failed");
306
236
  throw error;
307
- } finally {
308
- step("Disconnecting from Kubb Studio");
309
- shutdown.abort();
310
237
  }
311
238
  }
312
239
  const runner = async ({ values }) => {
313
240
  const options = {
314
- ...require_run.createStudioOptions(values),
241
+ configPath: values.config,
242
+ studioUrl: values.url ?? _kubb_studio.defaultStudioUrl,
243
+ logLevel: values.logLevel,
315
244
  token: values.token,
316
245
  id: values.id,
317
246
  baseId: values.baseId,
318
247
  name: values.name,
319
248
  packageVersion: values.packageVersion,
320
- timeout: values.timeout,
321
249
  json: values.json
322
250
  };
323
- await require_run.run(options, () => snapshot(options), { json: options.json });
251
+ await require_run.run({
252
+ options,
253
+ action: () => snapshot(options),
254
+ json: options.json
255
+ });
324
256
  };
325
257
  //#endregion
326
258
  exports.runner = runner;
327
259
 
328
- //# sourceMappingURL=snapshot-D4nKbwkw.cjs.map
260
+ //# sourceMappingURL=snapshot-BLEmwvP6.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-BLEmwvP6.cjs","names":["runtime","readFile","readFileSync","process","join","dirname","process","styleText","loadConfigs","logLevelMap","Hookable","cliReporter","createPlainLogger","createSnapshot","defaultStudioUrl","run"],"sources":["../../../internals/utils/src/fs.ts","../src/runners/studio/ci.ts","../src/runners/studio/snapshot.ts"],"sourcesContent":["import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return content\n }\n\n // Creating the directory up front costs a syscall per file, and every file after the first in a\n // directory pays it for nothing. Write first and only fall back when the directory is missing,\n // which also stays correct when something removed it mid-run.\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n\n if (options.sanity) {\n const savedData = await readFile(resolved, { encoding: 'utf-8' })\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Recursively removes `path`. Silently succeeds when `path` does not exist.\n *\n * @example\n * ```ts\n * await clean('./dist')\n * ```\n */\nexport async function clean(path: string): Promise<void> {\n return rm(path, { recursive: true, force: true })\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","import { readFileSync } from 'node:fs'\nimport process from 'node:process'\n\n/**\n * Stable identity for the CI agent a snapshot run registers or reuses.\n */\nexport type CiContext = {\n /**\n * Fed into the machine token, so the same merge or pull request reuses one agent.\n */\n id: string\n /**\n * Agent display name shown in Studio.\n */\n name: string\n /** The commit this run builds, so the next snapshot can diff against it. */\n commit?: string\n /** The branch a pull request merges into, and the `id` runs on that branch register under. */\n base?: { branch: string; id: string }\n}\n\ntype GithubPullRequest = { number: number; head?: { sha?: string }; base?: { ref?: string } }\n\nfunction readGithubPullRequest(eventPath: string | undefined): GithubPullRequest | undefined {\n if (!eventPath) {\n return undefined\n }\n\n try {\n const event = JSON.parse(readFileSync(eventPath, 'utf8')) as { pull_request?: GithubPullRequest }\n return event.pull_request?.number ? event.pull_request : undefined\n } catch {\n return undefined\n }\n}\n\n/** Reproduces GitLab's `CI_COMMIT_REF_SLUG` for a branch it gives no slug of, like a merge request's target. */\nexport function gitlabRefSlug(ref: string): string {\n return ref\n .toLowerCase()\n .replace(/[^a-z0-9]/gu, '-')\n .slice(0, 63)\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Detects the CI provider from its environment variables and derives a stable identity from it.\n * The GitHub row reproduces `gh:<repositoryId>:<prNumber>`, the identity `kubb-labs/action` has\n * always registered agents under, so an existing repository keeps reusing its agent.\n */\nexport function detectCi(env: Record<string, string | undefined> = process.env): CiContext | null {\n if (env.GITHUB_ACTIONS) {\n const pullRequest = readGithubPullRequest(env.GITHUB_EVENT_PATH)\n const repositoryId = env.GITHUB_REPOSITORY_ID ?? env.GITHUB_REPOSITORY ?? ''\n const branch = env.GITHUB_REF?.startsWith('refs/heads/') ? env.GITHUB_REF.slice('refs/heads/'.length) : undefined\n // A branch run is scoped by its ref, which a pull request number never collides with.\n const scope = pullRequest?.number ?? (branch ? `refs/heads/${branch}` : env.GITHUB_RUN_ID) ?? ''\n const baseBranch = pullRequest?.base?.ref\n\n return {\n id: `gh:${repositoryId}:${scope}`,\n name: `${env.GITHUB_REPOSITORY ?? 'github'}#${pullRequest?.number ?? branch ?? scope}`,\n // The head commit, not the merge commit a pull request run checks out.\n commit: pullRequest?.head?.sha ?? env.GITHUB_SHA,\n base: baseBranch ? { branch: baseBranch, id: `gh:${repositoryId}:refs/heads/${baseBranch}` } : undefined,\n }\n }\n\n if (env.GITLAB_CI) {\n const scope = env.CI_MERGE_REQUEST_IID ?? env.CI_COMMIT_REF_SLUG ?? ''\n const baseBranch = env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME\n\n return {\n id: `gl:${env.CI_PROJECT_ID ?? ''}:${scope}`,\n name: `${env.CI_PROJECT_PATH ?? 'gitlab'}#${scope}`,\n commit: env.CI_MERGE_REQUEST_SOURCE_BRANCH_SHA || env.CI_COMMIT_SHA,\n base: baseBranch ? { branch: baseBranch, id: `gl:${env.CI_PROJECT_ID ?? ''}:${gitlabRefSlug(baseBranch)}` } : undefined,\n }\n }\n\n if (env.BITBUCKET_BUILD_NUMBER) {\n const scope = env.BITBUCKET_PR_ID ?? env.BITBUCKET_BRANCH ?? ''\n\n return { id: `bb:${env.BITBUCKET_REPO_UUID ?? ''}:${scope}`, name: `${env.BITBUCKET_REPO_FULL_NAME ?? 'bitbucket'}#${scope}`, commit: env.BITBUCKET_COMMIT }\n }\n\n if (env.CIRCLECI) {\n // Owner-qualified: two projects with the same name under different CircleCI orgs must not\n // collide on one agent, since registering it purges the other project's live sessions.\n const owner = env.CIRCLE_PROJECT_USERNAME ?? ''\n const project = env.CIRCLE_PROJECT_REPONAME ?? 'circleci'\n const scope = env.CIRCLE_PR_NUMBER ?? env.CIRCLE_BRANCH ?? ''\n\n return { id: `circle:${owner}:${project}:${scope}`, name: `${owner ? `${owner}/` : ''}${project}#${scope}`, commit: env.CIRCLE_SHA1 }\n }\n\n return null\n}\n","import { dirname, join } from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { read } from '@internals/utils'\nimport { cliReporter, Hookable, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'\nimport { createSnapshot, defaultStudioUrl } from '@kubb/studio'\nimport type { BranchSnapshotChanges, PublishedSnapshot, SnapshotChanges } from '@kubb/studio/protocol'\nimport { logBlock } from '../../loggers/output.ts'\nimport { createPlainLogger } from '../../loggers/plainLogger.ts'\nimport setupReporters from '../../loggers/reporters.ts'\nimport { type CiContext, detectCi } from './ci.ts'\nimport { loadConfigs, run, type SnapshotOptions } from './run.ts'\nimport type { command } from '../../commands/studio/snapshot.ts'\nimport type { CommandRunner } from 'gunshi'\n\n/**\n * Walks up from `directory` to the first package.json with a name and a version.\n */\nasync function findPackageJson(directory: string): Promise<{ name: string; version: string }> {\n const packageJson = JSON.parse(await read(join(directory, 'package.json')).catch(() => '{}')) as Partial<{ name: string; version: string }>\n if (packageJson.name && packageJson.version) return { name: packageJson.name, version: packageJson.version }\n\n const parent = dirname(directory)\n if (parent === directory) throw new Error(`No package.json with a name and a version found above ${process.cwd()}`)\n\n return findPackageJson(parent)\n}\n\n/** Reads package.json only for whichever of name/version --name and --version did not supply. */\nasync function resolvePackageMetadata(options: SnapshotOptions): Promise<{ name: string; version: string }> {\n if (options.name && options.packageVersion) return { name: options.name, version: options.packageVersion }\n\n const found = await findPackageJson(process.cwd())\n return { name: options.name ?? found.name, version: options.packageVersion ?? found.version }\n}\n\nfunction resolveToken(options: SnapshotOptions): string {\n const token = options.token ?? process.env.KUBB_TOKEN\n\n if (!token) {\n throw new Error('An organization CI API key is required. Pass --token or set KUBB_TOKEN.')\n }\n\n return token\n}\n\nfunction resolveCiIdentity(options: SnapshotOptions): CiContext {\n const detected = detectCi()\n // A custom --id drops the detected base, since runs on that branch may use a custom id too.\n const ci = options.id ? { id: options.id, name: detected?.name ?? options.id, commit: detected?.commit } : detected\n\n if (!ci) {\n throw new Error('Could not detect a supported CI provider (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI). Pass --id.')\n }\n\n // An explicit --base-id may not name a branch, so it labels the comparison itself.\n const base = options.baseId ? { branch: options.baseId, id: options.baseId } : ci.base\n\n return { ...ci, base: base?.id === ci.id ? undefined : base }\n}\n\nfunction absoluteUrl(studioUrl: string, path: string): string {\n return new URL(path, `${studioUrl}/`).toString()\n}\n\ntype SnapshotResult = {\n id: string\n name: string | null\n version: string | null\n integrity: string | null\n url: string\n snapshotIdUrl: string\n expiresAt: string\n agentUrl: string\n /** What changed since the previous snapshot of this package on this agent. */\n changes?: SnapshotChanges\n /** What differs from the latest snapshot of the base branch, named by `branch`. */\n branchChanges?: BranchSnapshotChanges & { branch: string }\n}\n\nfunction toResult({ studioUrl, snapshot, ci }: { studioUrl: string; snapshot: PublishedSnapshot; ci: CiContext }): SnapshotResult {\n return {\n id: snapshot.id,\n name: snapshot.name,\n version: snapshot.version,\n integrity: snapshot.integrity,\n url: absoluteUrl(studioUrl, snapshot.url),\n snapshotIdUrl: absoluteUrl(studioUrl, snapshot.snapshotIdUrl),\n expiresAt: snapshot.expiresAt,\n agentUrl: absoluteUrl(studioUrl, `/agents/${snapshot.agent.slug ?? snapshot.agent.id}`),\n changes: snapshot.changes,\n branchChanges: snapshot.branchChanges && ci.base ? { ...snapshot.branchChanges, branch: ci.base.branch } : undefined,\n }\n}\n\n/** How many files differ, `against` what. */\nfunction describeChanges(changes: SnapshotChanges, against: string): string {\n const { added, changed, removed } = changes\n\n return added.length + changed.length + removed.length\n ? `${added.length} added, ${changed.length} changed, ${removed.length} removed ${against}`\n : `No changes ${against}`\n}\n\n/** One summary line: how many files changed, and since which run. */\nexport function formatChanges(changes: SnapshotChanges): string {\n if (!changes.base) {\n return 'First snapshot'\n }\n\n return describeChanges(changes, `since ${changes.base.commit?.slice(0, 7) ?? changes.base.createdAt}`)\n}\n\n/** One summary line: how the snapshot differs from the base branch's latest one. */\nexport function formatBranchChanges(changes: BranchSnapshotChanges & { branch: string }): string {\n if (changes.base) return describeChanges(changes, `against ${changes.branch}`)\n\n return changes.baseFound ? `No snapshot of ${changes.branch} for this package yet` : `No snapshot of ${changes.branch} to compare with`\n}\n\nfunction printSummary(result: SnapshotResult): void {\n logBlock([\n `${styleText('dim', 'Package'.padEnd(10))} ${result.name ?? '(unnamed)'}@${result.version ?? '0.0.0'}`,\n `${styleText('dim', 'Tarball'.padEnd(10))} ${styleText('cyan', result.url)}`,\n `${styleText('dim', 'Agent'.padEnd(10))} ${result.agentUrl}`,\n ...(result.branchChanges ? [`${styleText('dim', 'Branch'.padEnd(10))} ${formatBranchChanges(result.branchChanges)}`] : []),\n ...(result.changes ? [`${styleText('dim', 'Changes'.padEnd(10))} ${formatChanges(result.changes)}`] : []),\n `${styleText('dim', 'Expires'.padEnd(10))} ${result.expiresAt}`,\n ])\n}\n\n/**\n * Generates the project, packs it, and publishes it to Kubb Studio in one request, under the CI\n * agent the run's identity names.\n */\nexport async function snapshot(options: SnapshotOptions): Promise<void> {\n const token = resolveToken(options)\n const { config } = await loadConfigs(options)\n const ci = resolveCiIdentity(options)\n const { name, version: packageVersion } = await resolvePackageMetadata(options)\n const logLevel = logLevelMap[options.logLevel ?? 'info']\n\n const write = options.json ? (line: string) => console.error(line) : (line: string) => console.log(line)\n const hooks = new Hookable<KubbHooks>()\n setupReporters(hooks, { logLevel, reporters: [cliReporter], logger: createPlainLogger(write) })\n const step = (message: string) => {\n if (logLevel > logLevelMap.silent) {\n write(message)\n }\n }\n\n step('Generating the snapshot')\n\n try {\n const published = await createSnapshot({\n studioUrl: options.studioUrl,\n apiKey: token,\n config,\n hooks,\n id: ci.id,\n name: ci.name,\n packageName: name,\n version: packageVersion,\n commit: ci.commit,\n baseId: ci.base?.id,\n })\n const result = toResult({ studioUrl: options.studioUrl, snapshot: published, ci })\n\n step('Snapshot published')\n\n if (options.json) {\n console.log(JSON.stringify(result))\n }\n if (!options.json) {\n printSummary(result)\n }\n } catch (error) {\n step('Snapshot failed')\n throw error\n }\n}\n\nexport const runner: CommandRunner<{ args: typeof command.args; extensions: {} }> = async ({ values }) => {\n const options: SnapshotOptions = {\n configPath: values.config,\n studioUrl: values.url ?? defaultStudioUrl,\n logLevel: values.logLevel,\n token: values.token,\n id: values.id,\n baseId: values.baseId,\n name: values.name,\n packageVersion: values.packageVersion,\n json: values.json,\n }\n await run({ options, action: () => snapshot(options), json: options.json })\n}\n"],"mappings":"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"}
@@ -1,14 +1,30 @@
1
1
  import "./rolldown-runtime-CRm0XQPb.js";
2
- import { d as exists, f as read, i as loadConfigs, n as createStudioOptions, s as run } from "./run-Dgph0WYk.js";
3
- import { r as setupReporters } from "./config-8Xl4yZmw.js";
4
- import { h as createPlainLogger, r as logBlock } from "./output-B6Ondw0g.js";
2
+ import { i as loadConfigs, s as run } from "./run-B_0Vp1ZV.js";
3
+ import { i as runtime } from "./Telemetry-DqT0XYcN.js";
4
+ import { r as setupReporters } from "./config-CCxOcwWu.js";
5
+ import { h as createPlainLogger, r as logBlock } from "./output-CnSY0tCG.js";
5
6
  import { styleText } from "node:util";
6
- import { randomUUID } from "node:crypto";
7
7
  import process from "node:process";
8
+ import { readFile } from "node:fs/promises";
8
9
  import { dirname, join } from "node:path";
9
10
  import { readFileSync } from "node:fs";
10
- import { createAgent, createJob, machineTokenFrom, runConnection, waitForJob } from "@kubb/studio";
11
- import { cliReporter, logLevel } from "@kubb/core";
11
+ import { Hookable, cliReporter, logLevel } from "@kubb/core";
12
+ import { createSnapshot, defaultStudioUrl } from "@kubb/studio";
13
+ //#region ../../internals/utils/src/fs.ts
14
+ /**
15
+ * Reads the file at `path` as a UTF-8 string.
16
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const source = await read('./src/Pet.ts')
21
+ * ```
22
+ */
23
+ async function read(path) {
24
+ if (runtime.isBun) return Bun.file(path).text();
25
+ return readFile(path, { encoding: "utf8" });
26
+ }
27
+ //#endregion
12
28
  //#region src/runners/studio/ci.ts
13
29
  function readGithubPullRequest(eventPath) {
14
30
  if (!eventPath) return;
@@ -81,35 +97,17 @@ function detectCi(env = process.env) {
81
97
  //#endregion
82
98
  //#region src/runners/studio/snapshot.ts
83
99
  /**
84
- * How long to wait for Studio's `studio:ready` acknowledgement, above the client's own 10s
85
- * handshake-ack timeout.
100
+ * Walks up from `directory` to the first package.json with a name and a version.
86
101
  */
87
- const READY_TIMEOUT_MS = 15e3;
88
- /**
89
- * Longest `--timeout` accepted. Job records expire from Studio's storage after an hour.
90
- */
91
- const MAX_TIMEOUT_SECONDS = 3600;
92
- const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
93
- "localhost",
94
- "127.0.0.1",
95
- "::1"
96
- ]);
97
- async function findPackageJson(startDirectory) {
98
- let directory = startDirectory;
99
- for (;;) {
100
- const file = join(directory, "package.json");
101
- if (await exists(file)) {
102
- const packageJson = JSON.parse(await read(file));
103
- if (packageJson.name && packageJson.version) return {
104
- name: packageJson.name,
105
- version: packageJson.version
106
- };
107
- }
108
- const parent = dirname(directory);
109
- if (parent === directory) break;
110
- directory = parent;
111
- }
112
- throw new Error(`No package.json with a name and a version found above ${startDirectory}`);
102
+ async function findPackageJson(directory) {
103
+ const packageJson = JSON.parse(await read(join(directory, "package.json")).catch(() => "{}"));
104
+ if (packageJson.name && packageJson.version) return {
105
+ name: packageJson.name,
106
+ version: packageJson.version
107
+ };
108
+ const parent = dirname(directory);
109
+ if (parent === directory) throw new Error(`No package.json with a name and a version found above ${process.cwd()}`);
110
+ return findPackageJson(parent);
113
111
  }
114
112
  /** Reads package.json only for whichever of name/version --name and --version did not supply. */
115
113
  async function resolvePackageMetadata(options) {
@@ -117,10 +115,10 @@ async function resolvePackageMetadata(options) {
117
115
  name: options.name,
118
116
  version: options.packageVersion
119
117
  };
120
- const packageMetadata = await findPackageJson(process.cwd());
118
+ const found = await findPackageJson(process.cwd());
121
119
  return {
122
- name: options.name ?? packageMetadata.name,
123
- version: options.packageVersion ?? packageMetadata.version
120
+ name: options.name ?? found.name,
121
+ version: options.packageVersion ?? found.version
124
122
  };
125
123
  }
126
124
  function resolveToken(options) {
@@ -145,21 +143,10 @@ function resolveCiIdentity(options) {
145
143
  base: base?.id === ci.id ? void 0 : base
146
144
  };
147
145
  }
148
- /** Rejects a bad `--timeout` up front instead of letting it reach `waitForJob` as NaN or <= 0. */
149
- function resolveTimeoutMs(options) {
150
- const seconds = options.timeout ?? 600;
151
- if (!Number.isFinite(seconds) || seconds <= 0) throw new Error("--timeout must be a positive number of seconds");
152
- return Math.min(seconds, MAX_TIMEOUT_SECONDS) * 1e3;
153
- }
154
- /** The CI key is a credential, so refuse to send it anywhere but HTTPS or a loopback dev host. */
155
- function assertSecureStudioUrl(studioUrl) {
156
- const url = new URL(studioUrl);
157
- if (url.protocol !== "https:" && !LOOPBACK_HOSTS.has(url.hostname)) throw new Error(`Refusing to send the CI API key to ${studioUrl}. Use an https:// Studio URL, or a loopback host for local development.`);
158
- }
159
146
  function absoluteUrl(studioUrl, path) {
160
147
  return new URL(path, `${studioUrl}/`).toString();
161
148
  }
162
- function toResult(studioUrl, snapshot, agentSlug, ci) {
149
+ function toResult({ studioUrl, snapshot, ci }) {
163
150
  return {
164
151
  id: snapshot.id,
165
152
  name: snapshot.name,
@@ -168,7 +155,7 @@ function toResult(studioUrl, snapshot, agentSlug, ci) {
168
155
  url: absoluteUrl(studioUrl, snapshot.url),
169
156
  snapshotIdUrl: absoluteUrl(studioUrl, snapshot.snapshotIdUrl),
170
157
  expiresAt: snapshot.expiresAt,
171
- agentUrl: absoluteUrl(studioUrl, `/agents/${agentSlug}`),
158
+ agentUrl: absoluteUrl(studioUrl, `/agents/${snapshot.agent.slug ?? snapshot.agent.id}`),
172
159
  changes: snapshot.changes,
173
160
  branchChanges: snapshot.branchChanges && ci.base ? {
174
161
  ...snapshot.branchChanges,
@@ -202,126 +189,71 @@ function printSummary(result) {
202
189
  ]);
203
190
  }
204
191
  /**
205
- * Generates a Kubb Studio snapshot from a script: registers or reuses a CI agent, connects it,
206
- * queues a snapshot job, and polls until the tarball is ready. A snapshot job runs generation and
207
- * packs the tarball in one step, so this needs no separate generation run.
192
+ * Generates the project, packs it, and publishes it to Kubb Studio in one request, under the CI
193
+ * agent the run's identity names.
208
194
  */
209
195
  async function snapshot(options) {
210
- const timeoutMs = resolveTimeoutMs(options);
211
196
  const token = resolveToken(options);
212
- assertSecureStudioUrl(options.studioUrl);
213
- const { configPath } = await loadConfigs(options);
197
+ const { config } = await loadConfigs(options);
214
198
  const ci = resolveCiIdentity(options);
215
199
  const { name, version: packageVersion } = await resolvePackageMetadata(options);
216
200
  const logLevel$1 = logLevel[options.logLevel ?? "info"];
217
- process.env.KUBB_AGENT_SECRET = ci.id;
218
201
  const write = options.json ? (line) => console.error(line) : (line) => console.log(line);
219
- const logger = createPlainLogger(write);
202
+ const hooks = new Hookable();
203
+ setupReporters(hooks, {
204
+ logLevel: logLevel$1,
205
+ reporters: [cliReporter],
206
+ logger: createPlainLogger(write)
207
+ });
220
208
  const step = (message) => {
221
209
  if (logLevel$1 > logLevel.silent) write(message);
222
210
  };
223
- step("Creating Kubb Studio agent");
224
- const agent = await createAgent({
225
- studioUrl: options.studioUrl,
226
- token,
227
- name: ci.name,
228
- machineToken: machineTokenFrom(ci.id)
229
- });
230
- const instanceId = randomUUID();
231
- const { promise: ready, resolve: markReady } = Promise.withResolvers();
232
- const shutdown = new AbortController();
233
- const connection = runConnection({
234
- credentials: { token: agent.token },
235
- signal: shutdown.signal,
236
- clientOptions: () => ({
237
- studioUrl: options.studioUrl,
238
- instanceId,
239
- configPath,
240
- root: process.cwd(),
241
- version: options.version,
242
- permissions: options.permission,
243
- loadConfig: async () => (await loadConfigs(options)).config,
244
- installLogger: async (hooks) => {
245
- setupReporters(hooks, {
246
- logLevel: logLevel$1,
247
- reporters: [cliReporter],
248
- logger
249
- });
250
- hooks.hook("studio:ready", () => markReady());
251
- }
252
- }),
253
- onTokenRejected: ({ error }) => Promise.reject(error)
254
- });
255
- const connectionLost = new AbortController();
256
- const connectionEnded = /* @__PURE__ */ new Error("The Kubb Studio connection ended before the snapshot finished");
257
- connection.then(() => connectionLost.abort(connectionEnded), (error) => connectionLost.abort(error));
258
- const lost = connection.then(() => Promise.reject(connectionEnded), (error) => Promise.reject(error));
259
- lost.catch(() => {});
211
+ step("Generating the snapshot");
260
212
  try {
261
- let readyTimeout;
262
- try {
263
- await Promise.race([
264
- ready,
265
- lost,
266
- new Promise((_, reject) => {
267
- readyTimeout = setTimeout(() => reject(/* @__PURE__ */ new Error("Timed out waiting for Kubb Studio to confirm the agent was ready")), READY_TIMEOUT_MS);
268
- })
269
- ]);
270
- } finally {
271
- clearTimeout(readyTimeout);
272
- }
273
- step("Creating snapshot job");
274
- const snapshotDeadline = Date.now() + timeoutMs;
275
- const snapshotTimeoutMs = () => Math.max(snapshotDeadline - Date.now(), 1);
276
- const job = await createJob({
213
+ const published = await createSnapshot({
277
214
  studioUrl: options.studioUrl,
278
- token,
279
- type: "snapshot",
280
- agentId: agent.id,
281
- instanceId,
282
- name,
215
+ apiKey: token,
216
+ config,
217
+ hooks,
218
+ id: ci.id,
219
+ name: ci.name,
220
+ packageName: name,
283
221
  version: packageVersion,
284
222
  commit: ci.commit,
285
- baseId: ci.base?.id,
286
- timeoutMs: snapshotTimeoutMs(),
287
- signal: connectionLost.signal
223
+ baseId: ci.base?.id
288
224
  });
289
- step(`Snapshot job queued: ${job.id}`);
290
- const finished = await Promise.race([waitForJob({
225
+ const result = toResult({
291
226
  studioUrl: options.studioUrl,
292
- token,
293
- id: job.id,
294
- timeoutMs: snapshotTimeoutMs(),
295
- signal: connectionLost.signal
296
- }), lost]);
297
- if (finished.status === "failed") throw new Error(finished.error ?? "Snapshot job failed");
298
- if (!finished.snapshot) throw new Error("Snapshot job succeeded without a snapshot");
299
- const result = toResult(options.studioUrl, finished.snapshot, agent.slug, ci);
227
+ snapshot: published,
228
+ ci
229
+ });
300
230
  step("Snapshot published");
301
231
  if (options.json) console.log(JSON.stringify(result));
302
232
  if (!options.json) printSummary(result);
303
233
  } catch (error) {
304
234
  step("Snapshot failed");
305
235
  throw error;
306
- } finally {
307
- step("Disconnecting from Kubb Studio");
308
- shutdown.abort();
309
236
  }
310
237
  }
311
238
  const runner = async ({ values }) => {
312
239
  const options = {
313
- ...createStudioOptions(values),
240
+ configPath: values.config,
241
+ studioUrl: values.url ?? defaultStudioUrl,
242
+ logLevel: values.logLevel,
314
243
  token: values.token,
315
244
  id: values.id,
316
245
  baseId: values.baseId,
317
246
  name: values.name,
318
247
  packageVersion: values.packageVersion,
319
- timeout: values.timeout,
320
248
  json: values.json
321
249
  };
322
- await run(options, () => snapshot(options), { json: options.json });
250
+ await run({
251
+ options,
252
+ action: () => snapshot(options),
253
+ json: options.json
254
+ });
323
255
  };
324
256
  //#endregion
325
257
  export { runner };
326
258
 
327
- //# sourceMappingURL=snapshot-CkHXR0Nl.js.map
259
+ //# sourceMappingURL=snapshot-DVdepLl5.js.map