@kubb/studio 0.0.0-canary-20260903193839
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/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/configFile-DjzP1_Ln.cjs +575 -0
- package/dist/configFile-DjzP1_Ln.cjs.map +1 -0
- package/dist/configFile-ZnV5tPon.js +574 -0
- package/dist/configFile-ZnV5tPon.js.map +1 -0
- package/dist/index-BVn89Nw2.d.ts +562 -0
- package/dist/index.cjs +1330 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +274 -0
- package/dist/index.js +1319 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.cjs +42 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.js +37 -0
- package/dist/protocol.js.map +1 -0
- package/dist/resolveConfig-B9oGiNMi.js +179 -0
- package/dist/resolveConfig-B9oGiNMi.js.map +1 -0
- package/dist/resolveConfig-Ci-BVhN_.cjs +202 -0
- package/dist/resolveConfig-Ci-BVhN_.cjs.map +1 -0
- package/dist/rolldown-runtime-CRm0XQPb.js +9 -0
- package/dist/rolldown-runtime-qbf5tadS.cjs +38 -0
- package/package.json +86 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["spawn","detectTool","createStorage","fsDriver","styleText","randomBytes","process","hash","FetchError","styleText","x","detectUncachedTool","path","process","hash","createKubb","Diagnostics","styleText","ws","WebSocket","Diagnostics","styleText","process","Hookable","path","readFile","kubbVersion","version","isStudioPingMessage","isDisconnectMessage","isCommandMessage","mergePlugins","mergeAdapter","fsStorage","memoryStorage","writeFile","delay","styleText"],"sources":["../src/constants.ts","../../../internals/utils/src/errors.ts","../../../internals/utils/src/promise.ts","../../../internals/utils/src/tools.ts","../../../internals/utils/src/time.ts","../src/machine.ts","../src/api.ts","../package.json","../src/hooks.ts","../src/generate.ts","../src/ws.ts","../src/connectStudio.ts","../src/client.ts","../src/pair.ts"],"sourcesContent":["/**\n * Hosted Kubb Studio URL. Exported so credential stores can bind tokens to the resolved instance,\n * not whatever default the client would pick on its own.\n */\nexport const defaultStudioUrl = 'https://kubb.studio'\n\n/**\n * Defaults the Studio client uses when a host passes nothing.\n * Config path is left out on purpose: each host discovers that itself.\n */\nexport const agentDefaults = {\n studioUrl: defaultStudioUrl,\n retryIntervalMs: 30_000,\n /**\n * Maximum heartbeat interval. Studio drops agents from the active list after ~90s without a ping,\n * so a slower override would make a healthy agent look dead.\n */\n heartbeatIntervalMs: 30_000,\n poolSize: 1,\n} as const\n","/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new Error('Build failed', { cause: toError(err) })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n","/** A value that may already be resolved or still pending.\n *\n * @example\n * ```ts\n * function load(id: string): PossiblePromise<string> {\n * return cache.get(id) ?? fetchRemote(id)\n * }\n * ```\n */\nexport type PossiblePromise<T> = Promise<T> | T\n\n/** Returns `true` when `result` is a thenable `Promise`.\n *\n * @example\n * ```ts\n * isPromise(Promise.resolve(1)) // true\n * isPromise(42) // false\n * ```\n */\nexport function isPromise<T>(result: PossiblePromise<T>): result is Promise<T> {\n return result !== null && result !== undefined && typeof (result as Record<string, unknown>)['then'] === 'function'\n}\n\ntype Store<TKey, TValue> = {\n has(key: TKey): boolean\n get(key: TKey): TValue | undefined\n set(key: TKey, value: TValue): unknown\n}\n\n/**\n * Wraps `factory` with a keyed cache backed by the provided store.\n *\n * Pass a `WeakMap` for object keys (results are GC-eligible when the key is\n * collected) or a `Map` for primitive keys. For multi-argument functions,\n * nest two `memoize` calls — the outer keyed by the first argument, the\n * inner (created once per outer miss) keyed by the second.\n *\n * Because the cache is owned by the caller, it can be shared, inspected, or\n * cleared independently of the memoized function.\n *\n * @example Single WeakMap key\n * ```ts\n * const cache = new WeakMap<SchemaNode, Set<string>>()\n * const getRefs = memoize(cache, (node) => collectRefs(node))\n * ```\n *\n * @example Single Map key (primitive)\n * ```ts\n * const cache = new Map<string, Resolver>()\n * const getResolver = memoize(cache, (name) => buildResolver(name))\n * ```\n *\n * @example Two-level (object + primitive)\n * ```ts\n * const outer = new WeakMap<Params[], Map<string, Params[]>>()\n * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))\n * fn(params)('camelcase')\n * ```\n */\nexport function memoize<TKey, TValue>(store: Store<TKey, TValue>, factory: (key: TKey) => TValue): (key: TKey) => TValue {\n return (key: TKey): TValue => {\n if (store.has(key)) return store.get(key)!\n const value = factory(key)\n store.set(key, value)\n return value\n }\n}\n\ntype ParallelOptions<TItem> = {\n /**\n * Items to work through, handed out in order.\n */\n items: ReadonlyArray<TItem>\n /**\n * How many items may be in flight at once.\n */\n limit: number\n /**\n * Runs once per item, with the item's position so a caller can report progress.\n */\n run(item: TItem, index: number): Promise<void>\n}\n\n/**\n * Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each\n * takes the next item the moment it frees up instead of waiting for a batch to drain.\n *\n * @example\n * ```ts\n * await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })\n * ```\n */\nexport async function inParallel<TItem>({ items, limit, run }: ParallelOptions<TItem>): Promise<void> {\n const queue = items.entries()\n\n const worker = async (): Promise<void> => {\n for (const [index, item] of queue) await run(item, index)\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()))\n}\n","import { spawn } from 'node:child_process'\n\n/**\n * How one formatter or linter is invoked: the executable, the argv it takes for an output\n * directory, and what to report when it is not installed.\n */\nexport type ToolCommand = {\n command: string\n args: (outputPath: string) => Array<string>\n errorMessage: string\n}\n\n/**\n * CLI command descriptors for each supported code formatter.\n */\nexport const formatters = {\n prettier: {\n command: 'prettier',\n args: (outputPath: string) => ['--ignore-unknown', '--write', outputPath],\n errorMessage: 'Prettier not found',\n },\n biome: {\n command: 'biome',\n args: (outputPath: string) => ['format', '--write', outputPath],\n errorMessage: 'Biome not found',\n },\n oxfmt: {\n command: 'oxfmt',\n args: (outputPath: string) => [outputPath],\n errorMessage: 'Oxfmt not found',\n },\n} as const satisfies Record<string, ToolCommand>\n\n/**\n * CLI command descriptors for each supported linter.\n */\nexport const linters = {\n eslint: {\n command: 'eslint',\n args: (outputPath: string) => [outputPath, '--fix'],\n errorMessage: 'Eslint not found',\n },\n biome: {\n command: 'biome',\n args: (outputPath: string) => ['lint', '--fix', outputPath],\n errorMessage: 'Biome not found',\n },\n oxlint: {\n command: 'oxlint',\n // --no-ignore so oxlint lints the folder even when it's gitignored (generated output dirs usually are).\n args: (outputPath: string) => ['--fix', '--no-ignore', outputPath],\n errorMessage: 'Oxlint not found',\n },\n} as const satisfies Record<string, ToolCommand>\n\n/**\n * Preference order for `format: 'auto'`, most-preferred first. Spelled out rather than taken from\n * the table's key order, which is arbitrary and would silently change what `auto` picks.\n */\nexport const FORMATTER_PREFERENCE = ['oxfmt', 'biome', 'prettier'] as const\n\n/**\n * Preference order for `lint: 'auto'`, most-preferred first.\n */\nexport const LINTER_PREFERENCE = ['oxlint', 'biome', 'eslint'] as const\n\n/**\n * Whether `name` is on PATH and answers `--version` with a zero exit.\n */\nexport function isToolAvailable(name: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(name, ['--version'], { stdio: 'ignore' })\n child.on('close', (code) => resolve(code === 0))\n child.on('error', () => resolve(false))\n })\n}\n\n/**\n * Returns the first installed executable from `candidates`, or `null` when none are found.\n *\n * Not memoized: a long-running host that probes repeatedly should cache the result itself, and a\n * `--watch` build should keep noticing a tool installed mid-session.\n */\nexport async function detectTool<TName extends string>(candidates: ReadonlyArray<TName>): Promise<TName | null> {\n for (const candidate of candidates) {\n if (await isToolAvailable(candidate)) {\n return candidate\n }\n }\n\n return null\n}\n\n/**\n * Tokenizes a shell command string, respecting single and double quotes.\n *\n * @example\n * ```ts\n * tokenize('git commit -m \"initial commit\"')\n * // → ['git', 'commit', '-m', 'initial commit']\n * ```\n */\nexport function tokenize(command: string): Array<string> {\n return (command.match(/[^\\s\"']+|\"([^\"]*)\"|'([^']*)'/g) ?? []).map((token) => token.replace(/^[\"']|[\"']$/g, ''))\n}\n","/**\n * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.\n * Rounds to 2 decimal places for sub-millisecond precision without noise.\n *\n * @example\n * ```ts\n * const start = process.hrtime()\n * doWork()\n * getElapsedMs(start) // 42.35\n * ```\n */\nexport function getElapsedMs(hrStart: [number, number]): number {\n const [seconds, nanoseconds] = process.hrtime(hrStart)\n const ms = seconds * 1000 + nanoseconds / 1e6\n return Math.round(ms * 100) / 100\n}\n\n/**\n * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).\n *\n * @example\n * ```ts\n * formatMs(250) // '250ms'\n * formatMs(1500) // '1.50s'\n * formatMs(90000) // '1m 30.0s'\n * ```\n */\nexport function formatMs(ms: number): string {\n if (ms >= 60000) {\n const mins = Math.floor(ms / 60000)\n const secs = ((ms % 60000) / 1000).toFixed(1)\n return `${mins}m ${secs}s`\n }\n\n if (ms >= 1000) {\n return `${(ms / 1000).toFixed(2)}s`\n }\n return `${Math.round(ms)}ms`\n}\n","import { hash, randomBytes } from 'node:crypto'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { createStorage, type Storage } from 'unstorage'\nimport fsDriver from 'unstorage/drivers/fs'\n\n/**\n * Key-value storage the runtime uses for its machine secret and the last Studio config.\n *\n * One storage per process, since one process serves one config file. Hosts install their own\n * driver on startup: Nitro passes its `kubb` mount, the CLI an fs driver under `~/.kubb/cache`.\n * The in-memory default keeps the runtime usable without a host, at the cost of a machine\n * identity that changes on every restart.\n */\nlet storage: Storage = createStorage()\nlet hasInstalledStorage = false\n\n/**\n * Installs the storage driver the runtime persists to. Call once, before connecting.\n */\nexport function setStorage(next: Storage): void {\n storage = next\n hasInstalledStorage = true\n}\n\n/**\n * A storage backed by files under `base`, so the machine secret and the last Studio config\n * survive a restart. Repeated pairings of one machine depend on that secret staying put.\n */\nexport function createFileStorage(base: string): Storage {\n return createStorage({ driver: fsDriver({ base }) })\n}\n\nlet fallbackSecretPromise: Promise<string> | null = null\n\n/**\n * Loads the fallback machine secret from the runtime storage.\n * On first use it generates a secret and persists it, so the machine identity stays\n * stable across restarts. An identity that changes on every boot breaks session\n * creation with Studio whenever the startup registration call fails.\n */\nasync function loadOrCreateFallbackSecret(): Promise<string> {\n // The secret is memoized for the life of the process, so a host that reads the machine token\n // before installing its storage is bound to the throwaway in-memory default. Nothing else\n // surfaces that: the write succeeds, and the identity silently changes on every restart, which\n // Studio rejects with a 403 on the next session create.\n if (!hasInstalledStorage) {\n console.warn(\n styleText('yellow', 'Deriving the machine token before a storage driver was installed'),\n 'call setStorage() first, or set KUBB_AGENT_SECRET, to keep a stable machine identity across restarts',\n )\n }\n\n const stored = await storage.getItem('machine-secret').catch(() => null)\n\n if (typeof stored === 'string' && stored) {\n return stored\n }\n\n const secret = randomBytes(32).toString('hex')\n\n await storage.setItem('machine-secret', secret).catch(() => {\n console.warn(\n styleText('yellow', 'Could not persist the generated machine secret'),\n 'set KUBB_AGENT_SECRET to keep a stable machine identity across restarts',\n )\n })\n\n return secret\n}\n\n/**\n * Returns the machine token derived from the `KUBB_AGENT_SECRET` environment variable.\n * Falls back to a generated secret persisted in the runtime storage if the env var is not set.\n * The token is hashed with SHA-256.\n */\nexport async function getMachineToken(): Promise<string> {\n if (process.env.KUBB_AGENT_SECRET) {\n return hash('sha256', process.env.KUBB_AGENT_SECRET)\n }\n\n fallbackSecretPromise ??= loadOrCreateFallbackSecret()\n\n return hash('sha256', await fallbackSecretPromise)\n}\n","import { styleText } from 'node:util'\nimport { getErrorMessage } from '@internals/utils'\nimport { FetchError, ofetch } from 'ofetch'\nimport type { AgentConnectResponse } from './protocol/index.ts'\nimport { getMachineToken } from './machine.ts'\n\n/**\n * Reads a human-readable message from a Studio JSON error body, when it has one. `FetchError`'s own\n * message stops at the status line, so the detail Studio sends with a failure (an agent limit, a\n * revoked token) would otherwise never reach the user.\n */\nfunction responseMessage(data: unknown): string | undefined {\n if (!data || typeof data !== 'object') {\n return undefined\n }\n\n const body = data as { error_description?: unknown; message?: unknown; error?: unknown }\n for (const value of [body.error_description, body.message, body.error]) {\n if (typeof value === 'string' && value) {\n return value\n }\n }\n\n return undefined\n}\n\n/**\n * Retries after the first registration attempt, each backing off twice as far as the last.\n */\nconst REGISTER_RETRIES = 3\n\n/**\n * Shared in-flight registration so concurrent pool sessions trigger one purge, not N.\n */\nlet registrationInFlight: Promise<boolean> | null = null\n\ntype ConnectProps = {\n studioUrl: string\n token: string\n}\n\n/**\n * Thrown when Studio rejects the agent token itself (401). Retrying cannot help: the token was\n * revoked, or the agent it belonged to was deleted in the Studio UI. Hosts catch this to forget\n * the stored credential and pair again.\n */\nexport class InvalidAgentTokenError extends Error {\n constructor(studioUrl: string, options?: ErrorOptions) {\n super(`Kubb Studio rejected this agent's token. It was revoked or the agent was deleted in ${studioUrl}.`, options)\n this.name = 'InvalidAgentTokenError'\n }\n}\n\n/**\n * Whether a thrown value carries `statusCode`. Not narrowed to `FetchError`: a host wrapper can\n * throw its own error shape with the same field.\n *\n * A 401 means the agent token itself was rejected. A 403 from the session create endpoint means\n * the machine token stored in Studio no longer matches this agent (missing or mismatched).\n */\nfunction rejectedWith(error: unknown, statusCode: number): boolean {\n return (error as { statusCode?: number } | undefined)?.statusCode === statusCode\n}\n\nfunction sessionError(cause: unknown): Error {\n const detail = (cause instanceof FetchError ? responseMessage(cause.data) : undefined) ?? getErrorMessage(cause)\n return new Error(detail ? `Failed to get agent session from Kubb Studio: ${detail}` : 'Failed to get agent session from Kubb Studio', { cause })\n}\n\n/**\n * Performs the raw session create request against Studio.\n */\nasync function requestAgentSession({ token, studioUrl }: ConnectProps): Promise<AgentConnectResponse> {\n const url = `${studioUrl}/api/agent/sessions`\n\n const data = await ofetch<AgentConnectResponse>(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}` },\n body: { machineToken: await getMachineToken() },\n })\n\n if (!data) {\n throw new Error('No data available for agent session')\n }\n\n return data\n}\n\n/**\n * Obtain an agent session token from Kubb Studio via HTTP.\n *\n * When Studio rejects the machine token (403), for example after the agent restarted\n * with a new identity while the startup registration call failed, the agent re-registers\n * and retries once, so a single failed registration can't permanently block session creation.\n */\nexport async function createAgentSession({ token, studioUrl }: ConnectProps): Promise<AgentConnectResponse> {\n try {\n return await requestAgentSession({ token, studioUrl })\n } catch (error: unknown) {\n if (rejectedWith(error, 401)) {\n throw new InvalidAgentTokenError(studioUrl, { cause: error })\n }\n\n if (!rejectedWith(error, 403) || !(await registerAgent({ token, studioUrl }))) {\n throw sessionError(error)\n }\n\n try {\n return await requestAgentSession({ token, studioUrl })\n } catch (retryError: unknown) {\n if (rejectedWith(retryError, 401)) {\n throw new InvalidAgentTokenError(studioUrl, { cause: retryError })\n }\n\n throw sessionError(retryError)\n }\n }\n}\n\ntype RegisterProps = {\n studioUrl: string\n token: string\n poolSize?: number\n}\n\n/**\n * Register this agent with Kubb Studio by sending the machine ID.\n * Called on agent startup before creating a WebSocket session, and again when\n * Studio rejects the machine token during session creation.\n *\n * Retries with backoff because a failed registration leaves Studio with a stale\n * machine token that blocks every subsequent session create call. Registration\n * purges all of the agent's sessions on the Studio side, so concurrent callers\n * (multiple pool sessions hitting a 403 at once) share one in-flight run instead\n * of purging each other's fresh sessions.\n */\nexport function registerAgent(props: RegisterProps): Promise<boolean> {\n registrationInFlight ??= runRegistration(props).finally(() => {\n registrationInFlight = null\n })\n\n return registrationInFlight\n}\n\nasync function runRegistration({ token, studioUrl, poolSize }: RegisterProps): Promise<boolean> {\n const machineToken = await getMachineToken()\n\n try {\n await ofetch(`${studioUrl}/api/agent/connect`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n },\n body: { machineToken, poolSize },\n retry: REGISTER_RETRIES,\n // 2s, 4s, then 8s. `retry` counts down, so the first retry is the one with the most left.\n retryDelay: ({ options }) => 2_000 * 2 ** (REGISTER_RETRIES - Number(options.retry)),\n })\n\n return true\n } catch (error) {\n if (rejectedWith(error, 401)) {\n throw new InvalidAgentTokenError(studioUrl, { cause: error })\n }\n\n console.error(styleText('red', `Failed to register agent with Studio after ${REGISTER_RETRIES + 1} attempts`))\n\n return false\n }\n}\n\ntype DisconnectProps = {\n studioUrl: string\n token: string\n sessionId: string\n slug?: string | null\n}\n\n/**\n * Notify Kubb Studio that this agent is disconnecting.\n * Called on process termination or server close. A failed notify is logged and swallowed: the\n * local socket is already gone, and failing teardown must not block shutdown or reconnect.\n */\nexport async function disconnect({ sessionId, token, studioUrl, slug }: DisconnectProps): Promise<void> {\n const url = `${studioUrl}/api/agent/sessions/${sessionId}/disconnect`\n const tag = slug ?? 'agent'\n\n try {\n await ofetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n },\n })\n console.log(styleText('green', `[${tag}] Disconnected from Studio`))\n } catch (error) {\n console.warn(styleText('yellow', `[${tag}] Failed to notify Studio of disconnection: ${getErrorMessage(error)}`))\n }\n}\n","","import type { Hookable, KubbHooks } from '@kubb/core'\nimport { x } from 'tinyexec'\n\n/**\n * Events a host emits about its Kubb Studio session, as opposed to a generation. `kubb:` stays\n * reserved for generation lifecycle.\n */\nexport type StudioConnectingContext = {\n /**\n * The Studio instance this session is opening against.\n */\n url: string\n}\n\nexport type StudioConnectedContext = {\n /**\n * The Studio instance this session attached to.\n */\n url: string\n /**\n * Both sides of the connection, so a host can print them and make a mismatch visible.\n */\n versions: {\n /**\n * The Studio instance's own version, when it sent one.\n */\n studio?: string\n /**\n * The version of the runtime that connected.\n */\n kubb: string\n /**\n * The version of the host itself, such as the `kubb` CLI or the agent image.\n */\n agent: string\n }\n}\n\nexport type StudioDisconnectedContext = {\n /**\n * Why Studio ended the session.\n */\n reason: string\n}\n\nexport type StudioCommandStartContext = {\n /**\n * The command Studio sent, without its `studio:` prefix: `generate`, `connect` or `save`.\n */\n command: string\n}\n\nexport type StudioCommandEndContext = {\n /**\n * The command that finished, without its `studio:` prefix.\n */\n command: string\n /**\n * What the command did, when there is something to report: `applied 2/3 edits to kubb.config.ts`.\n */\n info?: string\n}\n\nexport type StudioWarnContext = {\n /**\n * What was refused or ignored, and what would change it.\n */\n message: string\n}\n\nexport type StudioErrorContext = {\n /**\n * The failure, for the host's own output. One Studio needs to hear about goes over the socket\n * through the `kubb:error` generation hook instead.\n */\n error: Error\n}\n\ndeclare global {\n namespace Kubb {\n interface KubbHooksRegistry {\n 'studio:connecting': [ctx: StudioConnectingContext]\n 'studio:connected': [ctx: StudioConnectedContext]\n 'studio:disconnected': [ctx: StudioDisconnectedContext]\n 'studio:command:start': [ctx: StudioCommandStartContext]\n 'studio:command:end': [ctx: StudioCommandEndContext]\n 'studio:warn': [ctx: StudioWarnContext]\n 'studio:error': [ctx: StudioErrorContext]\n }\n }\n}\n\n/**\n * Register a `kubb:hook:start` listener that spawns the requested command via tinyexec,\n * streams each stdout line as a `kubb:hook:line` event, and calls `kubb:hook:end` with the result.\n * Streaming the output lets Kubb Studio render live hook progress over the WebSocket connection.\n */\nexport function setupHookListener(hooks: Hookable<KubbHooks>, root: string): void {\n hooks.hook('kubb:hook:start', async (ctx) => {\n const { id, command, args } = ctx\n // No id means nothing is waiting on the result (benchmarks, tests).\n if (!id) {\n return\n }\n\n const commandWithArgs = args?.length ? `${command} ${args.join(' ')}` : command\n\n try {\n const proc = x(command, [...(args ?? [])], {\n nodeOptions: { cwd: root, detached: true },\n })\n\n for await (const line of proc) {\n await hooks.callHook('kubb:hook:line', { id, line })\n }\n\n const { exitCode } = await proc\n\n if (exitCode !== 0) {\n const error = new Error(`Hook execute failed: ${commandWithArgs}`)\n\n await hooks.callHook('kubb:hook:end', { id, command, args, success: false, error })\n await hooks.callHook('kubb:error', { error })\n\n return\n }\n\n await hooks.callHook('kubb:hook:end', { id, command, args, success: true, error: null })\n } catch (caughtError) {\n const error = new Error(`Hook execute failed: ${commandWithArgs}`)\n error.cause = caughtError\n\n await hooks.callHook('kubb:hook:end', { id, command, args, success: false, error })\n await hooks.callHook('kubb:error', { error })\n }\n })\n}\n\n/**\n * Waits for the `kubb:hook:end` matching `hookId`. Register this before calling `kubb:hook:start`:\n * `callHook` awaits its listeners, and {@link setupHookListener} calls `kubb:hook:end` from inside\n * that same listener, so a handler added afterward would already have missed it.\n */\nexport function waitForHookEnd(hooks: Hookable<KubbHooks>, hookId: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const handleHookEnd = (ctx: { id?: string; success: boolean; error?: Error | null }) => {\n if (ctx.id !== hookId) return\n hooks.removeHook('kubb:hook:end', handleHookEnd)\n\n if (ctx.success) {\n resolve()\n } else {\n reject(ctx.error)\n }\n }\n\n hooks.hook('kubb:hook:end', handleHookEnd)\n })\n}\n","import { hash } from 'node:crypto'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { type Config, createKubb, type Diagnostic, Diagnostics, type Hookable, type KubbHooks } from '@kubb/core'\nimport {\n FORMATTER_PREFERENCE,\n LINTER_PREFERENCE,\n formatters,\n linters,\n memoize,\n tokenize,\n type ToolCommand,\n detectTool as detectUncachedTool,\n} from '@internals/utils'\nimport { waitForHookEnd } from './hooks.ts'\n\n/**\n * `isToolAvailable` spawns a process, and a long-lived connection generates repeatedly, so each\n * executable is probed once per process. The CLI deliberately does not memoize: a `--watch` build\n * should keep noticing a tool installed mid-session.\n */\nconst detectTool = memoize(new Map<ReadonlyArray<string>, Promise<string | null>>(), detectUncachedTool)\n\n/**\n * The two post-build tool steps. Formatting and linting differ only in which tools they look for,\n * so they run through one loop rather than two near-identical blocks.\n *\n * `noun` and `verbing` are spelled out instead of built from `kind`. Concatenating `` `${kind}ter` ``\n * and `` `${kind}ting` `` works for `format`, but doubles the `t` in `lint`, giving \"lintter\" and\n * \"lintting\" instead of \"linter\" and \"linting\".\n */\nconst TOOL_STEPS: ReadonlyArray<{ kind: 'format' | 'lint'; noun: string; verbing: string; tools: Record<string, ToolCommand>; detect: ReadonlyArray<string> }> =\n [\n // `detect` is the preference order for `auto`, most-preferred first. Spelled out rather than\n // taken from the table's key order, which is arbitrary and silently changes what `auto` picks.\n { kind: 'format', noun: 'formatter', verbing: 'Formatting', tools: formatters, detect: FORMATTER_PREFERENCE },\n { kind: 'lint', noun: 'linter', verbing: 'Linting', tools: linters, detect: LINTER_PREFERENCE },\n ]\n\n/**\n * Absolute path of the directory the formatter and linter are pointed at.\n */\nfunction outputPath(config: Config): string {\n return path.isAbsolute(config.output.path) ? config.output.path : path.resolve(process.cwd(), config.root, config.output.path)\n}\n\ntype RunHookProps = {\n hooks: Hookable<KubbHooks>\n /**\n * Stable identity for the command, hashed so the `kubb:hook:*` events for concurrent commands\n * can be told apart.\n */\n id: string\n command: string\n args: ReadonlyArray<string>\n}\n\n/**\n * Emits `kubb:hook:start` and waits for the matching `kubb:hook:end`. The host spawns the process:\n * this only describes what to run and when it finished.\n *\n * @throws whatever the command failed with, so callers can report it their own way.\n */\nasync function runHook({ hooks, id, command, args }: RunHookProps): Promise<void> {\n const hookId = hash('sha256', id)\n // Registered before the start event, since `callHook` awaits its listeners and the host calls\n // `kubb:hook:end` from inside that same listener.\n const hookEnd = waitForHookEnd(hooks, hookId)\n\n await hooks.callHook('kubb:hook:start', { id: hookId, command, args: [...args] })\n await hookEnd\n}\n\ntype GenerateProps = {\n config: Config\n hooks: Hookable<KubbHooks>\n}\n\nfunction isProblemErrorDiagnostic(diagnostic: Diagnostic): diagnostic is Diagnostic & { plugin?: string; message: string } {\n return (diagnostic.kind ?? 'problem') === 'problem' && diagnostic.severity === 'error'\n}\n\n/**\n * Folds error-severity diagnostics into one thrown error so logs name the failing plugin.\n */\nfunction formatGenerationFailure(diagnostics: ReadonlyArray<Diagnostic>): Error {\n const reasons = diagnostics\n .filter(isProblemErrorDiagnostic)\n .map((diagnostic) => (diagnostic.plugin ? `${diagnostic.plugin}: ${diagnostic.message}` : diagnostic.message))\n\n if (!reasons.length) {\n return new Error('Generation failed')\n }\n\n return new Error(`Generation failed: ${reasons.length} error${reasons.length === 1 ? '' : 's'}: ${reasons.join('; ')}`)\n}\n\n/**\n * Runs a full Kubb code-generation cycle for the given config.\n *\n * Emits lifecycle events on the provided `hooks` emitter so callers (e.g. the WebSocket stream)\n * can forward progress to connected clients. After a successful build, auto-formatting and\n * linting are applied when configured, followed by any user-defined `hooks.done` commands.\n */\nexport async function generate({ config, hooks }: GenerateProps): Promise<void> {\n const hrStart = process.hrtime()\n\n await hooks.callHook('kubb:generation:start', { config })\n\n await hooks.callHook('kubb:info', { message: config.name ? `Setup generation ${config.name}` : 'Setup generation' })\n\n const kubb = createKubb(config, { hooks })\n await kubb.setup()\n\n await hooks.callHook('kubb:info', { message: config.name ? `Build generation ${config.name}` : 'Build generation' })\n\n const { files, diagnostics, storage } = await kubb.safeBuild()\n\n await hooks.callHook('kubb:info', { message: 'Load summary' })\n\n // Core captures build failures as `error`-severity diagnostics instead of throwing, so\n // surface each one as a `kubb:error` event for the client. Warnings and info reported\n // through `ctx.warn`/`ctx.info` reach the client directly via core's events on this shared emitter.\n for (const diagnostic of diagnostics.filter(isProblemErrorDiagnostic)) {\n await hooks.callHook('kubb:error', { error: new Error(diagnostic.plugin ? `${diagnostic.plugin}: ${diagnostic.message}` : diagnostic.message) })\n }\n\n const status = Diagnostics.hasError(diagnostics) ? 'failed' : 'success'\n\n await hooks.callHook('kubb:generation:end', {\n config,\n // Only the files Kubb generated. `fsStorage().readKeys()` lists the working directory, so\n // Studio's tree would show the host's own source and miss output landing outside it.\n storage: { ...storage, readKeys: async () => [...new Set(files.map((file) => file.path))] },\n diagnostics,\n status,\n hrStart,\n filesCreated: files.length,\n })\n\n if (status === 'failed') {\n throw formatGenerationFailure(diagnostics)\n }\n\n await hooks.callHook('kubb:success', { message: 'Generation successfully' })\n\n for (const step of TOOL_STEPS) {\n const setting = config.output[step.kind]\n if (!setting) {\n continue\n }\n\n await hooks.callHook(`kubb:${step.kind}:start`)\n\n // `auto` means \"whatever is installed\", so the tool is detected now rather than at config time.\n const tool = setting === 'auto' ? await detectTool(step.detect) : setting\n\n if (!tool) {\n await hooks.callHook('kubb:warn', { message: `No ${step.noun} found (${step.detect.join(', ')}). Skipping ${step.verbing.toLowerCase()}.` })\n }\n\n if (tool && setting === 'auto') {\n await hooks.callHook('kubb:info', { message: `Auto-detected ${step.noun}: ${styleText('dim', tool)}` })\n }\n\n const command = tool ? step.tools[tool] : undefined\n\n if (command) {\n try {\n await runHook({ hooks, id: [config.name, tool].filter(Boolean).join('-'), command: command.command, args: command.args(outputPath(config)) })\n\n await hooks.callHook('kubb:success', { message: `${step.verbing} with ${tool} successfully` })\n } catch (caughtError) {\n await hooks.callHook('kubb:error', { error: new Error(command.errorMessage, { cause: caughtError }) })\n }\n }\n\n await hooks.callHook(`kubb:${step.kind}:end`)\n }\n\n // `output.postGenerate` commands run in order, each one waiting for the previous to finish.\n if (config.output.postGenerate?.length) {\n await hooks.callHook('kubb:hooks:start')\n\n for (const entry of config.output.postGenerate) {\n const line = typeof entry === 'string' ? entry : entry.command\n const [cmd, ...args] = tokenize(line)\n\n if (!cmd) {\n continue\n }\n\n await runHook({ hooks, id: line, command: cmd, args })\n await hooks.callHook('kubb:success', { message: `${line} successfully executed` })\n }\n\n await hooks.callHook('kubb:hooks:end')\n }\n}\n","import { getElapsedMs, inParallel } from '@internals/utils'\nimport { Diagnostics, type Hookable, type KubbHooks } from '@kubb/core'\nimport WebSocket from 'ws'\nimport type { AgentMessage, DataMessagePayload } from './protocol/index.ts'\n\ntype WebSocketOptions = WebSocket.ClientOptions\n\n/**\n * How many generated files are read from storage at once when building the\n * `kubb:generation:end` payload. A spec producing thousands of files would otherwise fire one\n * `storage.readItem` per file simultaneously.\n */\nconst FILE_READ_CONCURRENCY = 50\n\n/**\n * How long the initial handshake may take before the socket is closed and the reconnect loop\n * takes over.\n */\nconst CONNECT_TIMEOUT_MS = 5_000\n\n/**\n * Per-socket event counter. Every data message carries the next value so Studio can restore the\n * agent's emission order even when the relay delivers frames out of order. Keyed by the socket so\n * the count stays monotonic across every generation run on one connection, and is dropped\n * automatically once the socket is collected.\n */\nconst eventSeqCounters = new WeakMap<WebSocket, number>()\n\nfunction nextEventSeq(ws: WebSocket): number {\n const seq = eventSeqCounters.get(ws) ?? 0\n eventSeqCounters.set(ws, seq + 1)\n\n return seq\n}\n\n/**\n * Opens a Studio WebSocket connection and closes it when the initial handshake exceeds the configured timeout.\n */\nexport function createWebsocket(url: string, options: WebSocketOptions): WebSocket {\n const ws = new WebSocket(url, options)\n\n const timer = setTimeout(() => {\n if (ws.readyState === WebSocket.CONNECTING) {\n ws.close(3008, 'Connection timeout')\n }\n }, CONNECT_TIMEOUT_MS)\n\n // Once the handshake settles the timer has nothing left to check, and leaving it pending holds\n // the socket for the rest of the window.\n ws.once('open', () => clearTimeout(timer))\n ws.once('close', () => clearTimeout(timer))\n\n return ws\n}\n\n/**\n * Sends a serialized agent message when the Studio socket is ready to accept frames.\n */\nexport function sendAgentMessage(ws: WebSocket, message: AgentMessage): void {\n try {\n if (ws.readyState !== WebSocket.OPEN) {\n return\n }\n\n ws.send(JSON.stringify(message))\n } catch (error) {\n throw new Error('Failed to send message to Kubb Studio', { cause: error })\n }\n}\n\n/**\n * Sends a single `kubb:error` payload to Studio, stamped from the same per-socket counter the event stream\n * uses so Studio can still order it against the generation events around it.\n */\nexport function sendErrorMessage(ws: WebSocket, error: Error): void {\n sendAgentMessage(ws, {\n type: 'agent:data',\n payload: { type: 'kubb:error', data: [{ message: error.message, stack: error.stack }], timestamp: Date.now(), seq: nextEventSeq(ws) },\n })\n}\n\n/**\n * Forwards selected Kubb lifecycle events to Studio as data messages for the active session.\n */\nexport function setupEventsStream(ws: WebSocket, hooks: Hookable<KubbHooks>): void {\n function sendDataMessage(payload: Omit<DataMessagePayload, 'seq' | 'timestamp'>) {\n sendAgentMessage(ws, {\n type: 'agent:data',\n payload: { ...payload, timestamp: Date.now(), seq: nextEventSeq(ws) },\n })\n }\n\n hooks.hook('kubb:plugin:start', (ctx) => {\n sendDataMessage({\n type: 'kubb:plugin:start',\n data: [{ plugin: ctx.plugin }],\n })\n })\n\n hooks.hook('kubb:plugin:end', (ctx) => {\n sendDataMessage({\n type: 'kubb:plugin:end',\n data: [{ plugin: ctx.plugin, duration: ctx.duration, success: ctx.success }],\n })\n })\n\n hooks.hook('kubb:build:start', ({ config, adapter }) => {\n sendDataMessage({\n type: 'kubb:build:start',\n data: [{ config: { name: config.name }, adapter: { name: adapter.name } }],\n })\n })\n\n hooks.hook('kubb:build:end', ({ files, outputDir }) => {\n sendDataMessage({\n type: 'kubb:build:end',\n data: [{ files: files.map((file) => ({ path: file.path, name: file.name })), outputDir }],\n })\n })\n\n hooks.hook('kubb:files:processing:start', ({ files }) => {\n sendDataMessage({\n type: 'kubb:files:processing:start',\n data: [{ total: files.length }],\n })\n })\n\n hooks.hook('kubb:files:processing:update', ({ files }) => {\n sendDataMessage({\n type: 'kubb:files:processing:update',\n data: [\n {\n files: files.map(({ file, processed, total, percentage }) => ({\n file: file.path,\n processed,\n total,\n percentage,\n })),\n },\n ],\n })\n })\n\n hooks.hook('kubb:files:processing:end', ({ files }) => {\n sendDataMessage({\n type: 'kubb:files:processing:end',\n data: [{ total: files.length }],\n })\n })\n\n // The three log levels differ only in their event name.\n for (const type of ['kubb:info', 'kubb:success', 'kubb:warn'] as const) {\n hooks.hook(type, ({ message, info }) => {\n sendDataMessage({ type, data: [{ message, info }] })\n })\n }\n\n hooks.hook('kubb:generation:start', ({ config }) => {\n sendDataMessage({\n type: 'kubb:generation:start',\n data: [\n {\n name: config.name,\n plugins: config.plugins.length,\n },\n ],\n })\n })\n\n hooks.hook('kubb:generation:end', async ({ config, storage, diagnostics = [], status, hrStart, filesCreated }) => {\n const paths = await storage.readKeys()\n const files: Record<string, string> = {}\n await inParallel({\n items: paths,\n limit: FILE_READ_CONCURRENCY,\n run: async (path) => {\n const content = await storage.readItem(path)\n if (content !== null) files[path] = content\n },\n })\n\n sendDataMessage({\n type: 'kubb:generation:end',\n data: [{ config, storage: files }],\n })\n\n if (!hrStart) {\n return\n }\n\n const duration = Math.round(getElapsedMs(hrStart))\n\n sendDataMessage({\n type: 'kubb:generation:summary',\n data: [{ duration, fileCount: filesCreated ?? 0, failedPlugins: Diagnostics.failedPlugins(diagnostics).length, status: status ?? 'success' }],\n })\n })\n\n hooks.hook('kubb:error', ({ error }) => {\n sendDataMessage({\n type: 'kubb:error',\n data: [\n {\n message: error.message,\n stack: error.stack,\n },\n ],\n })\n })\n\n // Bracketing events carry no context, so they forward identically.\n for (const type of [\n 'kubb:lifecycle:start',\n 'kubb:lifecycle:end',\n 'kubb:format:start',\n 'kubb:format:end',\n 'kubb:lint:start',\n 'kubb:lint:end',\n 'kubb:hooks:start',\n 'kubb:hooks:end',\n ] as const) {\n hooks.hook(type, () => {\n sendDataMessage({ type, data: [] })\n })\n }\n\n hooks.hook('kubb:hook:start', ({ id, command, args }) => {\n sendDataMessage({\n type: 'kubb:hook:start',\n data: [{ id, command, args: args ? [...args] : undefined }],\n })\n })\n\n hooks.hook('kubb:hook:line', ({ id, line }) => {\n sendDataMessage({\n type: 'kubb:hook:line',\n data: [{ id, line }],\n })\n })\n\n hooks.hook('kubb:hook:end', ({ id, command, args, success, error }) => {\n sendDataMessage({\n type: 'kubb:hook:end',\n data: [\n {\n id,\n command,\n args: args ? [...args] : undefined,\n success,\n error: error ? { message: error.message, stack: error.stack } : undefined,\n },\n ],\n })\n })\n}\n","import { readFile, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { getErrorMessage, toError } from '@internals/utils'\nimport { type Config, fsStorage, Hookable, type KubbHooks, memoryStorage } from '@kubb/core'\nimport { version as kubbVersion } from '../package.json'\nimport { setupHookListener } from './hooks.ts'\nimport { type AgentMessage, type ClientInfo, type ConfigFileView, isCommandMessage, isDisconnectMessage, isStudioPingMessage } from './protocol/index.ts'\nimport { createAgentSession, disconnect, InvalidAgentTokenError } from './api.ts'\nimport { generate } from './generate.ts'\nimport { agentDefaults } from './constants.ts'\nimport { mergeAdapter, mergePlugins } from './resolveConfig.ts'\nimport type WebSocket from 'ws'\nimport { createWebsocket, sendAgentMessage, sendErrorMessage, setupEventsStream } from './ws.ts'\n\nexport type ConnectToStudioOptions = {\n token: string\n studioUrl?: string\n configPath: string\n /**\n * Loads the on-disk Kubb config. Injected so each host resolves config its own way: the Docker\n * agent from an explicit `KUBB_AGENT_CONFIG` path, the CLI through the same discovery\n * `kubb generate` uses.\n */\n loadConfig: () => Promise<Config>\n /**\n * The runtime's own version, reported to Studio next to the `kubb` version.\n */\n version: string\n /**\n * Identifies the host to Studio, so the UI can badge a CLI connection and show the real project.\n */\n client?: ClientInfo\n allowWrite?: boolean\n /**\n * Whether Studio may edit the project's `kubb.config.ts`. Granted separately from `allowWrite`,\n * which only covers generated output: this rewrites a file the user wrote by hand.\n */\n allowConfigEdit?: boolean\n allowInput?: boolean\n /**\n * Whether the formatter, the linter, and `output.postGenerate` may run as child processes.\n * Defaults to true, which is what the Docker agent has always done. The CLI runs in the user's\n * own project, so it defaults this off and asks before granting it.\n */\n allowExec?: boolean\n root?: string\n retryInterval?: number\n heartbeatInterval?: number\n /**\n * Number of pool sessions this agent serves. Read by `createClient`, which opens one\n * `connectToStudio` per slot, and reported to Studio at registration.\n */\n poolSize?: number\n /**\n * Aborting this disconnects the session and stops the reconnect loop. Hosts wire it to their own\n * shutdown: Nitro's `close` hook, or `SIGINT`/`SIGTERM` in the CLI.\n */\n signal?: AbortSignal\n /**\n * Installs listeners on an event emitter, once for the session and once per generation. Left out,\n * the runtime prints nothing, which is what a library should default to.\n */\n installLogger?: (hooks: Hookable<KubbHooks>) => void | Promise<void>\n}\n\n/**\n * Schedules another connection attempt.\n *\n * Hoisted out of `connectToStudio` on purpose: a pending retry timer reaches its whole enclosing\n * scope, so keeping it inside would pin the closed socket, the hook emitter, and the session id\n * alive for the length of every retry interval.\n */\nfunction reconnect(options: ConnectToStudioOptions): void {\n const { signal, retryInterval = agentDefaults.retryIntervalMs } = options\n\n if (signal?.aborted) {\n return\n }\n\n console.info(styleText('dim', `Retrying connection in ${retryInterval}ms to Kubb Studio ...`))\n\n const cancel = () => clearTimeout(timer)\n const timer = setTimeout(() => {\n // Removed here rather than left to `{ once: true }`: the signal only aborts at shutdown, so one\n // listener per retry would accumulate for the whole life of a down-Studio retry loop.\n signal?.removeEventListener('abort', cancel)\n\n if (signal?.aborted) {\n return\n }\n\n // The rejection is never awaited, so it has to be caught here or it surfaces as an\n // unhandledRejection that kills the retry loop instead of trying again.\n connectToStudio(options).catch((error: unknown) => {\n console.error(styleText('red', `Reconnect attempt to Kubb Studio failed: ${getErrorMessage(error)}`))\n\n // A rejected token stays rejected, so retrying only spams 401s until the process is killed.\n if (error instanceof InvalidAgentTokenError) {\n return\n }\n\n reconnect(options)\n })\n }, retryInterval)\n\n signal?.addEventListener('abort', cancel, { once: true })\n}\n\nexport async function connectToStudio(options: ConnectToStudioOptions): Promise<void> {\n const {\n token,\n studioUrl = agentDefaults.studioUrl,\n configPath,\n loadConfig,\n version,\n client,\n // Every permission is off unless the host grants it.\n allowWrite = false,\n allowConfigEdit = false,\n allowInput = false,\n allowExec = false,\n root = process.cwd(),\n heartbeatInterval: requestedHeartbeatInterval = agentDefaults.heartbeatIntervalMs,\n signal,\n installLogger,\n } = options\n\n // Studio counts an agent offline once its last ping is older than its liveness window, so a\n // slower cadence would make a healthy agent invisible. Clamped here rather than in a host's env\n // parsing, so every host is held to the contract.\n const heartbeatInterval = Math.min(requestedHeartbeatInterval, agentDefaults.heartbeatIntervalMs)\n\n // Each connection gets its own isolated event emitter so generation events\n // from one session do not bleed into another session's WebSocket stream.\n const hooks = new Hookable<KubbHooks>()\n await installLogger?.(hooks)\n\n try {\n // Before the session exists, so a host can cover the wait: `createAgentSession` is a round\n // trip and the socket after it opens without being awaited.\n await hooks.callHook('studio:connecting', { url: studioUrl })\n\n const { sessionId, slug, wsUrl, isSandbox, version: sessionStudioVersion } = await createAgentSession({ token, studioUrl })\n\n // Known before the agent announces itself, and refreshed by a later `studio:connect`, so both\n // sides can be named from the first connect on.\n let studioVersion = sessionStudioVersion\n const ws = createWebsocket(wsUrl, {\n headers: { Authorization: `Bearer ${token}` },\n })\n\n // Effective permissions: always disabled in sandbox mode\n const canWrite = isSandbox ? false : allowWrite\n // A sandbox has no user project to edit, so a config edit is never granted there.\n const canEditConfig = isSandbox ? false : allowConfigEdit\n // `configPath` is relative to the agent's root unless it is already absolute, which is what\n // `resolve` does on its own.\n const configFilePath = path.resolve(root, configPath)\n // A sandbox agent always generates from the spec Studio supplies; a local agent only when opted in.\n const canUseInput = isSandbox || allowInput\n // Tracks whether the studio server explicitly disconnected us (no reconnect needed)\n let serverDisconnected = false\n // Guards against a second `generate` command starting while one is already running.\n // Without this, two concurrent `generate()` calls share this socket via `setupEventsStream`,\n // and their events interleave with no way for Studio to tell the two runs apart.\n let isGenerating = false\n let heartbeatTimer: ReturnType<typeof setInterval> | undefined\n // Tracks socket liveness: Studio replies to every ping with a pong. When pongs stop\n // arriving the connection is half-open (e.g. dropped during a Studio deploy) and must\n // be terminated so the reconnect loop can establish a fresh session.\n let lastPongAt = Date.now()\n\n const onAbort = () => void teardown({ reason: 'shutdown', retry: false })\n\n function cleanup(reason = 'cleanup') {\n clearInterval(heartbeatTimer)\n heartbeatTimer = undefined\n\n // This connection is over, so its shutdown listener must go with it. Otherwise every\n // reconnect leaves one behind on a signal that only fires at process exit.\n signal?.removeEventListener('abort', onAbort)\n\n hooks.removeAllHooks()\n\n try {\n ws.close(1000, reason)\n } catch {}\n\n ws.removeEventListener('open', onOpen)\n ws.removeEventListener('close', onClose)\n ws.removeEventListener('error', onError)\n ws.removeEventListener('message', onMessage)\n }\n\n /**\n * Reads `kubb.config.ts` and reports which plugin options Studio may edit.\n *\n * Skipped when the host did not grant `allowConfigEdit`. The patcher pulls in `magicast`\n * (~25ms, ~55MB RSS), so read-only agents never import it.\n *\n * Not cached: the user can edit the file between two Studio actions.\n */\n async function readConfigFileView(source?: string): Promise<ConfigFileView | undefined> {\n if (!canEditConfig) {\n return undefined\n }\n\n try {\n const { readConfig } = await import('./configFile.ts')\n\n return readConfig(source ?? (await readFile(configFilePath, 'utf-8')))\n } catch (error) {\n await hooks.callHook('studio:warn', { message: `Could not read ${configFilePath}: ${getErrorMessage(error)}` })\n\n return undefined\n }\n }\n\n async function sendConnectedPayload() {\n const config = await loadConfig()\n\n sendAgentMessage(ws, {\n type: 'agent:connect',\n payload: {\n versions: { kubb: kubbVersion, agent: version },\n root,\n config: {\n path: configPath,\n file: await readConfigFileView(),\n plugins: config.plugins.map((plugin) => ({\n name: `@kubb/${plugin.name}`,\n // Functions and symbols in plugin options are dropped by `JSON.stringify` on the way out.\n options: plugin.options ?? {},\n })),\n },\n permissions: {\n allowWrite: canWrite,\n allowInput: canUseInput,\n allowExec,\n allowConfigEdit: canEditConfig,\n },\n },\n })\n }\n\n async function handleOpen() {\n lastPongAt = Date.now()\n await hooks.callHook('studio:connected', { url: studioUrl, versions: { studio: studioVersion, kubb: kubbVersion, agent: version } })\n\n // Announce readiness without waiting for a `studio:connect` command. The command from the\n // Studio UI is lost when it is sent while the agent is not attached to the session\n // (e.g. reconnecting after a deploy), so the agent introduces itself on every open.\n try {\n await sendConnectedPayload()\n } catch (error) {\n await hooks.callHook('studio:warn', { message: `Failed to send the connect payload: ${getErrorMessage(error)}` })\n }\n }\n\n // `addEventListener` drops the returned promise, so a host whose logger throws would take the\n // process down with an unhandled rejection instead of just losing a line of output. Nothing is\n // left to report it with at that point, which is why this swallows.\n const onOpen = () => void handleOpen().catch(() => {})\n\n /**\n * Drops the socket and tells Studio the session is over. `serverDisconnected` guards against\n * the close event running this a second time, and against a shutdown reconnecting.\n */\n async function teardown({ reason, retry }: { reason?: string; retry: boolean }) {\n if (serverDisconnected) {\n return\n }\n serverDisconnected = true\n\n // Announce the shutdown while the socket is still open, so Studio marks the session offline\n // now instead of waiting out the heartbeat window. `sendAgentMessage` is a no-op on a socket\n // that has already closed, which is every other way we get here.\n if (reason === 'shutdown') {\n sendAgentMessage(ws, { type: 'agent:disconnect', reason: 'shutdown' })\n }\n\n cleanup(reason)\n // Already tearing down, so a failed disconnect changes nothing.\n await disconnect({ sessionId, studioUrl, token, slug }).catch(() => {})\n\n if (retry) {\n reconnect(options)\n }\n }\n\n const onClose = () => teardown({ retry: true })\n\n const onError = () => {\n void hooks.callHook('studio:error', { error: new Error('Failed to connect to Kubb Studio') })\n\n return onClose()\n }\n\n ws.addEventListener('open', onOpen)\n ws.addEventListener('close', onClose)\n ws.addEventListener('error', onError)\n // The socket's own close event fires after this, and `teardown` is idempotent, so the shutdown\n // path cannot be turned into a reconnect by the close that follows it.\n signal?.addEventListener('abort', onAbort, { once: true })\n\n heartbeatTimer = setInterval(() => {\n // Two consecutive missed pongs mean the socket is dead even though no close event\n // arrived. Terminate (not close) so a half-open TCP connection can't linger. The\n // resulting close event triggers cleanup and the reconnect loop.\n if (Date.now() - lastPongAt > heartbeatInterval * 2) {\n void hooks.callHook('studio:warn', { message: 'No reply from Kubb Studio, terminating the stale connection' })\n // Stop the timer here rather than waiting for cleanup(), since the close event can lag,\n // and until it runs this interval would re-terminate and re-log every tick.\n clearInterval(heartbeatTimer)\n heartbeatTimer = undefined\n ws.terminate()\n\n return\n }\n\n sendAgentMessage(ws, { type: 'agent:ping' })\n }, heartbeatInterval)\n\n // Only `kubb:error` is ever fired on the connection emitter. Every generation event goes\n // through its own emitter below, so it gets that one listener rather than the full stream.\n hooks.hook('kubb:error', ({ error }) => sendErrorMessage(ws, error))\n\n const onMessage = async (message: WebSocket.MessageEvent) => {\n try {\n const data = JSON.parse(message.data as string) as AgentMessage\n\n if (isStudioPingMessage(data)) {\n lastPongAt = Date.now()\n\n return\n }\n\n if (isDisconnectMessage(data)) {\n await hooks.callHook('studio:disconnected', { reason: data.reason })\n\n if (data.reason === 'revoked') {\n cleanup(`session_${data.reason}`)\n return\n }\n\n if (data.reason === 'expired') {\n cleanup()\n reconnect(options)\n\n return\n }\n\n return\n }\n\n if (isCommandMessage(data)) {\n // Every command type is `studio:<verb>`, so the verb alone is what a host wants to show.\n const command = data.type.slice('studio:'.length)\n\n await hooks.callHook('studio:command:start', { command })\n\n if (data.type === 'studio:generate') {\n if (isGenerating) {\n await hooks.callHook('studio:warn', { message: 'Ignored generate: a generation is already in progress' })\n\n await Promise.resolve(\n hooks.callHook('kubb:error', { error: new Error('A generation is already in progress, please wait for it to finish') }),\n ).catch(() => {})\n\n return\n }\n\n isGenerating = true\n\n try {\n const config = await loadConfig()\n const patch = data.payload\n const plugins = await mergePlugins(config.plugins, patch?.plugins)\n const adapter = await mergeAdapter(config.adapter, patch?.adapter)\n\n // A sandbox agent always uses the inline spec (empty string included, since it has no disk\n // file); a local agent only when opted in, and an empty or absent spec falls back to disk.\n const inputOverride = isSandbox ? (patch?.input ?? '') : (allowInput && patch?.input) || undefined\n\n if (allowWrite && isSandbox) {\n await hooks.callHook('studio:warn', { message: 'Running in a sandbox, so writing files is disabled' })\n }\n\n if (patch?.input && !canUseInput) {\n // The Docker agent reads `allowInput` from `KUBB_AGENT_ALLOW_INPUT`. The CLI grants it\n // through `--allowInput` or the per-project prompt instead, so each host gets its own remedy.\n const remedy = client?.kind === 'cli' ? '--allowInput, or answer yes when kubb studio asks,' : 'KUBB_AGENT_ALLOW_INPUT=true'\n await hooks.callHook('studio:warn', { message: `Ignored the spec from Studio; set ${remedy} to generate from it` })\n }\n\n const generationHooks = new Hookable<KubbHooks>()\n await installLogger?.(generationHooks)\n setupHookListener(generationHooks, root)\n setupEventsStream(ws, generationHooks)\n\n const resolvedPlugins = plugins ?? config.plugins\n\n await generate({\n config: {\n ...config,\n root,\n input: inputOverride ?? config.input,\n storage: canWrite ? fsStorage() : memoryStorage(),\n output: allowExec ? { ...config.output } : { ...config.output, format: false, lint: false, postGenerate: [] },\n plugins: resolvedPlugins,\n adapter,\n },\n hooks: generationHooks,\n })\n\n await hooks.callHook('studio:command:end', {\n command,\n info: `${resolvedPlugins.length} plugin${resolvedPlugins.length === 1 ? '' : 's'}, ${canWrite ? 'written to disk' : 'in memory'}${inputOverride !== undefined ? ', from a Studio spec' : ''}`,\n })\n } finally {\n isGenerating = false\n }\n\n return\n }\n\n if (data.type === 'studio:connect') {\n studioVersion = data.version ?? studioVersion\n await sendConnectedPayload()\n\n await hooks.callHook('studio:command:end', { command })\n\n return\n }\n\n if (data.type === 'studio:save') {\n // Studio waits on an `agent:save` for every `studio:save`, so every path out of this\n // branch sends one. `edits` is checked before it is walked: the message crosses the same\n // trust boundary as the values inside it.\n if (!Array.isArray(data.edits)) {\n await hooks.callHook('studio:warn', { message: 'Ignored save: the message carried no edits' })\n\n sendAgentMessage(ws, { type: 'agent:save', payload: { outcomes: [], changed: false } })\n\n return\n }\n\n const edits = data.edits\n const refuse = (reason: string) =>\n sendAgentMessage(ws, {\n type: 'agent:save',\n payload: { outcomes: edits.map((edit) => ({ edit, applied: false, reason })), changed: false },\n })\n\n if (!canEditConfig) {\n await hooks.callHook('studio:warn', { message: 'Ignored save: editing kubb.config.ts was not granted' })\n\n refuse('the agent was not granted permission to edit kubb.config.ts')\n\n return\n }\n\n // A generation reloads the config while it runs, so rewriting the file underneath it\n // would leave that run working from half the change.\n if (isGenerating) {\n refuse('a generation is in progress')\n\n return\n }\n\n try {\n // Read straight before the patch rather than reusing what went out on connect. The user\n // may have edited the file since, and since every untouched node keeps its own text,\n // patching what is on disk right now preserves that edit.\n const { applyConfigEdits } = await import('./configFile.ts')\n const current = await readFile(configFilePath, 'utf-8')\n const { source: patched, outcomes, changed } = applyConfigEdits(current, edits)\n\n if (changed) {\n await writeFile(configFilePath, patched, 'utf-8')\n }\n\n sendAgentMessage(ws, {\n type: 'agent:save',\n payload: { outcomes, changed, file: changed ? await readConfigFileView(patched) : undefined },\n })\n\n const applied = outcomes.filter((outcome) => outcome.applied).length\n await hooks.callHook('studio:command:end', { command, info: `applied ${applied}/${outcomes.length} edits to ${configPath}` })\n } catch (error) {\n // An unreadable config, a read-only filesystem. Reported as a refusal of every edit so\n // Studio hears back rather than waiting on a reply that never comes.\n await hooks.callHook('studio:error', { error: toError(error) })\n\n refuse(getErrorMessage(error))\n }\n\n return\n }\n\n return\n }\n\n await hooks.callHook('studio:warn', { message: `Ignored an unknown message from Kubb Studio: ${data.type}` })\n } catch (error) {\n await hooks.callHook('studio:error', { error: toError(error) })\n\n // Errors thrown before `generate()` runs (e.g. config loading, plugin resolution)\n // never reach `generate()`'s own `kubb:error` emission, so without this the Studio\n // UI shows nothing while the agent silently fails. Forward them on the connection-level\n // `hooks` emitter, already wired to this socket via `setupEventsStream`.\n await Promise.resolve(hooks.callHook('kubb:error', { error: toError(error) })).catch(() => {})\n }\n }\n ws.addEventListener('message', onMessage)\n } catch (error) {\n // Reaching here means the session was never created (Studio down, a 502 mid-deploy), so no\n // socket exists and none of the socket-driven reconnect paths can fire. Retry from here or the\n // slot is dropped for the lifetime of the process.\n await hooks.callHook('studio:error', { error: toError(error) })\n\n if (error instanceof InvalidAgentTokenError) {\n throw error\n }\n\n reconnect(options)\n }\n}\n","import type { Storage } from 'unstorage'\nimport { agentDefaults } from './constants.ts'\nimport { registerAgent } from './api.ts'\nimport { type ConnectToStudioOptions, connectToStudio } from './connectStudio.ts'\nimport { setStorage } from './machine.ts'\n\nexport type ClientOptions = Omit<ConnectToStudioOptions, 'signal'> & {\n /**\n * Where the machine secret and the last Studio config are persisted. Defaults to in-memory,\n * which gives up a stable machine identity across restarts.\n */\n storage?: Storage\n}\n\nexport type Client = {\n /**\n * Registers with Studio and opens the session pool. Resolves once the pool is starting: the\n * sessions keep running, and reconnect on their own, until `disconnect` is called.\n */\n connect: () => Promise<void>\n /**\n * Closes every session and stops reconnecting.\n */\n disconnect: () => void\n}\n\n/**\n * Creates the Kubb Studio client: the connection, the command loop, and the generation event\n * stream shared by the `kubb studio` CLI command and the Docker agent.\n *\n * Every permission is off by default. A host that wants more grants it explicitly.\n *\n * @example\n * ```ts\n * const studio = createClient({ token, configPath, version, loadConfig: () => loadMyConfig() })\n * await studio.connect()\n * ```\n */\nexport function createClient({ storage, ...options }: ClientOptions): Client {\n if (storage) {\n setStorage(storage)\n }\n\n const controller = new AbortController()\n const poolSize = options.poolSize ?? agentDefaults.poolSize\n\n return {\n async connect() {\n await registerAgent({ token: options.token, studioUrl: options.studioUrl ?? agentDefaults.studioUrl, poolSize })\n\n // Each slot is its own session, so one Studio user never sees another's generation events.\n // Awaited: `connectToStudio` only ever rejects with `InvalidAgentTokenError` (every other\n // failure is retried internally through its own reconnect loop and resolves normally), so\n // awaiting here surfaces a dead token to the caller without blocking on a down Studio.\n await Promise.all(Array.from({ length: poolSize }, () => connectToStudio({ ...options, signal: controller.signal })))\n },\n disconnect() {\n controller.abort()\n },\n }\n}\n","import { setTimeout as delay } from 'node:timers/promises'\nimport { styleText } from 'node:util'\nimport { getErrorMessage } from '@internals/utils'\nimport { ofetch } from 'ofetch'\nimport { agentDefaults } from './constants.ts'\nimport { getMachineToken } from './machine.ts'\n\n/**\n * RFC 8628 device-authorization response from Studio's `/api/auth/device/code` endpoint.\n * Field names match the RFC; the CLI polls with `device_code` and shows `user_code` to the user.\n */\nexport type PairingSession = {\n device_code: string\n user_code: string\n verification_uri: string\n verification_uri_complete: string\n expires_in: number\n interval: number\n}\n\nexport type PairingResult = {\n /**\n * Bearer token for this machine. Write credentials with mode 0600 and never log the value.\n */\n token: string\n agent: {\n /**\n * Stable agent id in Studio.\n */\n id: string\n /**\n * Short slug used in logs and the UI (for example `brave-otter`).\n */\n slug: string\n /**\n * Display name chosen at pairing time.\n */\n name: string\n }\n}\n\n/**\n * Identifies the CLI to Studio's device authorization endpoint. A label, not a secret: what\n * authorizes a pairing is a signed-in person approving the code in the browser.\n */\nconst CLIENT_ID = 'kubb-cli'\n\ntype StartPairingOptions = {\n studioUrl?: string\n /**\n * Display name for the agent, usually the project or machine name.\n */\n name: string\n hostname: string\n /**\n * Which client is pairing. Defaults to the CLI, where any signed-in member may approve their own\n * machine. The Docker image passes `kubb-agent`, whose codes only an admin can approve.\n */\n clientId?: string\n /**\n * What a `kubb-agent` pairing asks to be registered as. Studio rejects the request without it,\n * and ignores it for the CLI.\n */\n agentKind?: 'user' | 'sandbox'\n}\n\n/**\n * Asks Studio for a pairing code. The machine token travels with the request and is stored against\n * the code, so approval knows which machine it is pairing: the same machine pairing twice rotates\n * one agent's token instead of creating a second agent.\n */\nexport async function startPairing({\n studioUrl = agentDefaults.studioUrl,\n name,\n hostname,\n clientId = CLIENT_ID,\n agentKind,\n}: StartPairingOptions): Promise<PairingSession> {\n return ofetch<PairingSession>(`${studioUrl}/api/auth/device/code`, {\n method: 'POST',\n body: {\n client_id: clientId,\n name,\n hostname,\n machine_token: await getMachineToken(),\n agent_kind: agentKind,\n },\n })\n}\n\ntype PollOptions = {\n studioUrl?: string\n session: PairingSession\n}\n\ntype PollError = 'authorization_pending' | 'slow_down' | 'expired_token' | 'access_denied' | 'invalid_grant'\n\ntype PollResponse =\n | PairingResult\n | {\n error: PollError | string\n /**\n * Why, when Studio has something more useful to say than the RFC code. An approval that hits\n * the organization's agent limit comes back as `access_denied` with the limit spelled out.\n */\n error_description?: string\n }\n\nfunction isPairingResult(response: PollResponse | undefined): response is PairingResult {\n return !!response && typeof response === 'object' && 'token' in response && typeof response.token === 'string'\n}\n\n/**\n * Polls until the user approves or denies, honoring the server's `slow_down` back-off. A poll that\n * cannot reach Studio is warned about and retried, since the code stays valid either way.\n *\n * Studio's own endpoint is used rather than the auth layer's `/device/token`, because an approved\n * Kubb pairing is worth an agent bearer token, not a user session.\n *\n * @throws when the code expires, the user denies it, or Studio returns an unexpected error.\n */\nexport async function pollForPairingToken({ studioUrl = agentDefaults.studioUrl, session }: PollOptions): Promise<PairingResult> {\n // Both fields cross the network, so neither is trusted as-is: a missing or zero `interval` would\n // spin the poll loop, and a missing or zero `expires_in` would expire the code before the first\n // poll. `> 0` is also false for `NaN` and for a missing field, so it doubles as the type guard.\n const deadline = Date.now() + (session.expires_in > 0 ? session.expires_in : 600) * 1000\n let intervalMs = (session.interval > 0 ? session.interval : 5) * 1000\n\n while (Date.now() < deadline) {\n await delay(intervalMs)\n\n let response: PollResponse | undefined\n try {\n response = await ofetch<PollResponse | undefined>(`${studioUrl}/api/agent/token`, {\n method: 'POST',\n body: { device_code: session.device_code },\n // A denial, an expiry, and \"not yet\" all come back as 4xx with a body the caller needs to\n // read, so let every response through and switch on `error` instead of catching.\n ignoreResponseError: true,\n })\n } catch (error) {\n // Studio can go briefly unreachable (a deploy, a dropped connection) during the minutes the\n // user has to approve in the browser. One failed poll should not end a pairing whose code is\n // still valid, so warn and try again on the next tick, the way `registerAgent` retries.\n console.warn(styleText('yellow', `Could not reach Kubb Studio while waiting for approval, retrying: ${getErrorMessage(error)}`))\n continue\n }\n\n if (isPairingResult(response)) {\n return response\n }\n\n if (!response || typeof response !== 'object' || !('error' in response) || typeof response.error !== 'string') {\n throw new Error('Kubb Studio returned an empty pairing response, pair again')\n }\n\n if (response.error === 'authorization_pending') {\n continue\n }\n\n if (response.error === 'slow_down') {\n intervalMs += 5_000\n continue\n }\n\n if (response.error === 'access_denied') {\n throw new Error(response.error_description ?? 'Pairing was denied in the browser')\n }\n\n if (response.error === 'expired_token' || response.error === 'invalid_grant') {\n throw new Error(response.error_description ?? 'The pairing code expired, pair again')\n }\n\n throw new Error(response.error_description ?? `Pairing failed (${response.error})`)\n }\n\n throw new Error('The pairing code expired, pair again')\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
+
import { s as ClientInfo } from "./index-BVn89Nw2.js";
|
|
3
|
+
import { Storage } from "unstorage";
|
|
4
|
+
import { Config, Hookable, KubbHooks } from "@kubb/core";
|
|
5
|
+
//#region src/connectStudio.d.ts
|
|
6
|
+
type ConnectToStudioOptions = {
|
|
7
|
+
token: string;
|
|
8
|
+
studioUrl?: string;
|
|
9
|
+
configPath: string;
|
|
10
|
+
/**
|
|
11
|
+
* Loads the on-disk Kubb config. Injected so each host resolves config its own way: the Docker
|
|
12
|
+
* agent from an explicit `KUBB_AGENT_CONFIG` path, the CLI through the same discovery
|
|
13
|
+
* `kubb generate` uses.
|
|
14
|
+
*/
|
|
15
|
+
loadConfig: () => Promise<Config>;
|
|
16
|
+
/**
|
|
17
|
+
* The runtime's own version, reported to Studio next to the `kubb` version.
|
|
18
|
+
*/
|
|
19
|
+
version: string;
|
|
20
|
+
/**
|
|
21
|
+
* Identifies the host to Studio, so the UI can badge a CLI connection and show the real project.
|
|
22
|
+
*/
|
|
23
|
+
client?: ClientInfo;
|
|
24
|
+
allowWrite?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether Studio may edit the project's `kubb.config.ts`. Granted separately from `allowWrite`,
|
|
27
|
+
* which only covers generated output: this rewrites a file the user wrote by hand.
|
|
28
|
+
*/
|
|
29
|
+
allowConfigEdit?: boolean;
|
|
30
|
+
allowInput?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the formatter, the linter, and `output.postGenerate` may run as child processes.
|
|
33
|
+
* Defaults to true, which is what the Docker agent has always done. The CLI runs in the user's
|
|
34
|
+
* own project, so it defaults this off and asks before granting it.
|
|
35
|
+
*/
|
|
36
|
+
allowExec?: boolean;
|
|
37
|
+
root?: string;
|
|
38
|
+
retryInterval?: number;
|
|
39
|
+
heartbeatInterval?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Number of pool sessions this agent serves. Read by `createClient`, which opens one
|
|
42
|
+
* `connectToStudio` per slot, and reported to Studio at registration.
|
|
43
|
+
*/
|
|
44
|
+
poolSize?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Aborting this disconnects the session and stops the reconnect loop. Hosts wire it to their own
|
|
47
|
+
* shutdown: Nitro's `close` hook, or `SIGINT`/`SIGTERM` in the CLI.
|
|
48
|
+
*/
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
/**
|
|
51
|
+
* Installs listeners on an event emitter, once for the session and once per generation. Left out,
|
|
52
|
+
* the runtime prints nothing, which is what a library should default to.
|
|
53
|
+
*/
|
|
54
|
+
installLogger?: (hooks: Hookable<KubbHooks>) => void | Promise<void>;
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/client.d.ts
|
|
58
|
+
type ClientOptions = Omit<ConnectToStudioOptions, 'signal'> & {
|
|
59
|
+
/**
|
|
60
|
+
* Where the machine secret and the last Studio config are persisted. Defaults to in-memory,
|
|
61
|
+
* which gives up a stable machine identity across restarts.
|
|
62
|
+
*/
|
|
63
|
+
storage?: Storage;
|
|
64
|
+
};
|
|
65
|
+
type Client = {
|
|
66
|
+
/**
|
|
67
|
+
* Registers with Studio and opens the session pool. Resolves once the pool is starting: the
|
|
68
|
+
* sessions keep running, and reconnect on their own, until `disconnect` is called.
|
|
69
|
+
*/
|
|
70
|
+
connect: () => Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Closes every session and stops reconnecting.
|
|
73
|
+
*/
|
|
74
|
+
disconnect: () => void;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Creates the Kubb Studio client: the connection, the command loop, and the generation event
|
|
78
|
+
* stream shared by the `kubb studio` CLI command and the Docker agent.
|
|
79
|
+
*
|
|
80
|
+
* Every permission is off by default. A host that wants more grants it explicitly.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const studio = createClient({ token, configPath, version, loadConfig: () => loadMyConfig() })
|
|
85
|
+
* await studio.connect()
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function createClient({ storage, ...options }: ClientOptions): Client;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/hooks.d.ts
|
|
91
|
+
/**
|
|
92
|
+
* Events a host emits about its Kubb Studio session, as opposed to a generation. `kubb:` stays
|
|
93
|
+
* reserved for generation lifecycle.
|
|
94
|
+
*/
|
|
95
|
+
type StudioConnectingContext = {
|
|
96
|
+
/**
|
|
97
|
+
* The Studio instance this session is opening against.
|
|
98
|
+
*/
|
|
99
|
+
url: string;
|
|
100
|
+
};
|
|
101
|
+
type StudioConnectedContext = {
|
|
102
|
+
/**
|
|
103
|
+
* The Studio instance this session attached to.
|
|
104
|
+
*/
|
|
105
|
+
url: string;
|
|
106
|
+
/**
|
|
107
|
+
* Both sides of the connection, so a host can print them and make a mismatch visible.
|
|
108
|
+
*/
|
|
109
|
+
versions: {
|
|
110
|
+
/**
|
|
111
|
+
* The Studio instance's own version, when it sent one.
|
|
112
|
+
*/
|
|
113
|
+
studio?: string;
|
|
114
|
+
/**
|
|
115
|
+
* The version of the runtime that connected.
|
|
116
|
+
*/
|
|
117
|
+
kubb: string;
|
|
118
|
+
/**
|
|
119
|
+
* The version of the host itself, such as the `kubb` CLI or the agent image.
|
|
120
|
+
*/
|
|
121
|
+
agent: string;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
type StudioDisconnectedContext = {
|
|
125
|
+
/**
|
|
126
|
+
* Why Studio ended the session.
|
|
127
|
+
*/
|
|
128
|
+
reason: string;
|
|
129
|
+
};
|
|
130
|
+
type StudioCommandStartContext = {
|
|
131
|
+
/**
|
|
132
|
+
* The command Studio sent, without its `studio:` prefix: `generate`, `connect` or `save`.
|
|
133
|
+
*/
|
|
134
|
+
command: string;
|
|
135
|
+
};
|
|
136
|
+
type StudioCommandEndContext = {
|
|
137
|
+
/**
|
|
138
|
+
* The command that finished, without its `studio:` prefix.
|
|
139
|
+
*/
|
|
140
|
+
command: string;
|
|
141
|
+
/**
|
|
142
|
+
* What the command did, when there is something to report: `applied 2/3 edits to kubb.config.ts`.
|
|
143
|
+
*/
|
|
144
|
+
info?: string;
|
|
145
|
+
};
|
|
146
|
+
type StudioWarnContext = {
|
|
147
|
+
/**
|
|
148
|
+
* What was refused or ignored, and what would change it.
|
|
149
|
+
*/
|
|
150
|
+
message: string;
|
|
151
|
+
};
|
|
152
|
+
type StudioErrorContext = {
|
|
153
|
+
/**
|
|
154
|
+
* The failure, for the host's own output. One Studio needs to hear about goes over the socket
|
|
155
|
+
* through the `kubb:error` generation hook instead.
|
|
156
|
+
*/
|
|
157
|
+
error: Error;
|
|
158
|
+
};
|
|
159
|
+
declare global {
|
|
160
|
+
namespace Kubb {
|
|
161
|
+
interface KubbHooksRegistry {
|
|
162
|
+
'studio:connecting': [ctx: StudioConnectingContext];
|
|
163
|
+
'studio:connected': [ctx: StudioConnectedContext];
|
|
164
|
+
'studio:disconnected': [ctx: StudioDisconnectedContext];
|
|
165
|
+
'studio:command:start': [ctx: StudioCommandStartContext];
|
|
166
|
+
'studio:command:end': [ctx: StudioCommandEndContext];
|
|
167
|
+
'studio:warn': [ctx: StudioWarnContext];
|
|
168
|
+
'studio:error': [ctx: StudioErrorContext];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/api.d.ts
|
|
174
|
+
/**
|
|
175
|
+
* Thrown when Studio rejects the agent token itself (401). Retrying cannot help: the token was
|
|
176
|
+
* revoked, or the agent it belonged to was deleted in the Studio UI. Hosts catch this to forget
|
|
177
|
+
* the stored credential and pair again.
|
|
178
|
+
*/
|
|
179
|
+
declare class InvalidAgentTokenError extends Error {
|
|
180
|
+
constructor(studioUrl: string, options?: ErrorOptions);
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/constants.d.ts
|
|
184
|
+
/**
|
|
185
|
+
* Hosted Kubb Studio URL. Exported so credential stores can bind tokens to the resolved instance,
|
|
186
|
+
* not whatever default the client would pick on its own.
|
|
187
|
+
*/
|
|
188
|
+
declare const defaultStudioUrl = "https://kubb.studio";
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/machine.d.ts
|
|
191
|
+
/**
|
|
192
|
+
* Installs the storage driver the runtime persists to. Call once, before connecting.
|
|
193
|
+
*/
|
|
194
|
+
declare function setStorage(next: Storage): void;
|
|
195
|
+
/**
|
|
196
|
+
* A storage backed by files under `base`, so the machine secret and the last Studio config
|
|
197
|
+
* survive a restart. Repeated pairings of one machine depend on that secret staying put.
|
|
198
|
+
*/
|
|
199
|
+
declare function createFileStorage(base: string): Storage;
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/pair.d.ts
|
|
202
|
+
/**
|
|
203
|
+
* RFC 8628 device-authorization response from Studio's `/api/auth/device/code` endpoint.
|
|
204
|
+
* Field names match the RFC; the CLI polls with `device_code` and shows `user_code` to the user.
|
|
205
|
+
*/
|
|
206
|
+
type PairingSession = {
|
|
207
|
+
device_code: string;
|
|
208
|
+
user_code: string;
|
|
209
|
+
verification_uri: string;
|
|
210
|
+
verification_uri_complete: string;
|
|
211
|
+
expires_in: number;
|
|
212
|
+
interval: number;
|
|
213
|
+
};
|
|
214
|
+
type PairingResult = {
|
|
215
|
+
/**
|
|
216
|
+
* Bearer token for this machine. Write credentials with mode 0600 and never log the value.
|
|
217
|
+
*/
|
|
218
|
+
token: string;
|
|
219
|
+
agent: {
|
|
220
|
+
/**
|
|
221
|
+
* Stable agent id in Studio.
|
|
222
|
+
*/
|
|
223
|
+
id: string;
|
|
224
|
+
/**
|
|
225
|
+
* Short slug used in logs and the UI (for example `brave-otter`).
|
|
226
|
+
*/
|
|
227
|
+
slug: string;
|
|
228
|
+
/**
|
|
229
|
+
* Display name chosen at pairing time.
|
|
230
|
+
*/
|
|
231
|
+
name: string;
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
type StartPairingOptions = {
|
|
235
|
+
studioUrl?: string;
|
|
236
|
+
/**
|
|
237
|
+
* Display name for the agent, usually the project or machine name.
|
|
238
|
+
*/
|
|
239
|
+
name: string;
|
|
240
|
+
hostname: string;
|
|
241
|
+
/**
|
|
242
|
+
* Which client is pairing. Defaults to the CLI, where any signed-in member may approve their own
|
|
243
|
+
* machine. The Docker image passes `kubb-agent`, whose codes only an admin can approve.
|
|
244
|
+
*/
|
|
245
|
+
clientId?: string;
|
|
246
|
+
/**
|
|
247
|
+
* What a `kubb-agent` pairing asks to be registered as. Studio rejects the request without it,
|
|
248
|
+
* and ignores it for the CLI.
|
|
249
|
+
*/
|
|
250
|
+
agentKind?: 'user' | 'sandbox';
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Asks Studio for a pairing code. The machine token travels with the request and is stored against
|
|
254
|
+
* the code, so approval knows which machine it is pairing: the same machine pairing twice rotates
|
|
255
|
+
* one agent's token instead of creating a second agent.
|
|
256
|
+
*/
|
|
257
|
+
declare function startPairing({ studioUrl, name, hostname, clientId, agentKind }: StartPairingOptions): Promise<PairingSession>;
|
|
258
|
+
type PollOptions = {
|
|
259
|
+
studioUrl?: string;
|
|
260
|
+
session: PairingSession;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Polls until the user approves or denies, honoring the server's `slow_down` back-off. A poll that
|
|
264
|
+
* cannot reach Studio is warned about and retried, since the code stays valid either way.
|
|
265
|
+
*
|
|
266
|
+
* Studio's own endpoint is used rather than the auth layer's `/device/token`, because an approved
|
|
267
|
+
* Kubb pairing is worth an agent bearer token, not a user session.
|
|
268
|
+
*
|
|
269
|
+
* @throws when the code expires, the user denies it, or Studio returns an unexpected error.
|
|
270
|
+
*/
|
|
271
|
+
declare function pollForPairingToken({ studioUrl, session }: PollOptions): Promise<PairingResult>;
|
|
272
|
+
//#endregion
|
|
273
|
+
export { type Client, type ClientOptions, InvalidAgentTokenError, type StudioCommandEndContext, type StudioCommandStartContext, type StudioConnectedContext, type StudioConnectingContext, type StudioDisconnectedContext, type StudioErrorContext, type StudioWarnContext, createClient, createFileStorage, defaultStudioUrl, pollForPairingToken, setStorage, startPairing };
|
|
274
|
+
//# sourceMappingURL=index.d.ts.map
|