@kubb/cli 5.10.0 → 5.10.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.
- package/dist/{Telemetry-BmdDiGPs.js → Telemetry-4smY7ClN.js} +11 -3
- package/dist/Telemetry-4smY7ClN.js.map +1 -0
- package/dist/{Telemetry-DuLxs5c3.cjs → Telemetry-DOahDJXF.cjs} +11 -3
- package/dist/Telemetry-DOahDJXF.cjs.map +1 -0
- package/dist/{background-DWasKvSw.js → background-BKWslHZJ.js} +30 -7
- package/dist/background-BKWslHZJ.js.map +1 -0
- package/dist/{background-y0JpYwlj.js → background-BkDRe558.js} +1 -1
- package/dist/{background-CK9olfbv.cjs → background-CF80MwuR.cjs} +25 -6
- package/dist/background-CF80MwuR.cjs.map +1 -0
- package/dist/{config-BPuFepc5.js → config-BC4Wm0OK.js} +19 -5
- package/dist/config-BC4Wm0OK.js.map +1 -0
- package/dist/{config-CGUQ39wR.cjs → config-bF-Lo5C2.cjs} +18 -4
- package/dist/config-bF-Lo5C2.cjs.map +1 -0
- package/dist/index.cjs +65 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +65 -24
- package/dist/index.js.map +1 -1
- package/dist/{output-Ck7iT223.cjs → output-C3zLt8M9.cjs} +104 -43
- package/dist/output-C3zLt8M9.cjs.map +1 -0
- package/dist/{output-DIJsbXzW.js → output-CxcaLxy9.js} +99 -44
- package/dist/output-CxcaLxy9.js.map +1 -0
- package/dist/{registry-CHJCPOAR.js → registry-C83t41nj.js} +50 -16
- package/dist/registry-C83t41nj.js.map +1 -0
- package/dist/{registry-gERiCBje.cjs → registry-CzU92tJd.cjs} +49 -15
- package/dist/registry-CzU92tJd.cjs.map +1 -0
- package/dist/{run-Ds-xI766.cjs → run-BKAAkdxB.cjs} +22 -16
- package/dist/run-BKAAkdxB.cjs.map +1 -0
- package/dist/{run-DNa_bKZE.js → run-CRvjGIa_.js} +22 -16
- package/dist/run-CRvjGIa_.js.map +1 -0
- package/dist/{run-DPqVZIX_.cjs → run-D6HyzEsH.cjs} +2 -2
- package/dist/{run-DPqVZIX_.cjs.map → run-D6HyzEsH.cjs.map} +1 -1
- package/dist/{run-B33l-Tx7.js → run-DsRPwmkb.js} +27 -14
- package/dist/run-DsRPwmkb.js.map +1 -0
- package/dist/{run-DORetnI8.js → run-Fdkc4Q6n.js} +6 -4
- package/dist/run-Fdkc4Q6n.js.map +1 -0
- package/dist/{run-B2kL57dE.js → run-eMVMu3Y-.js} +1 -1
- package/dist/{run-C3v9G5i3.cjs → run-eNvhTNiz.cjs} +6 -4
- package/dist/run-eNvhTNiz.cjs.map +1 -0
- package/dist/{run-BUZPvsbR.js → run-wI0Rk7F-.js} +2 -2
- package/dist/{run-BUZPvsbR.js.map → run-wI0Rk7F-.js.map} +1 -1
- package/dist/{run-Bqy3HW6o.cjs → run-xXgz6PA_.cjs} +27 -14
- package/dist/run-xXgz6PA_.cjs.map +1 -0
- package/dist/{snapshot-C4TMtjON.cjs → snapshot-Bk0S2Myh.cjs} +22 -9
- package/dist/snapshot-Bk0S2Myh.cjs.map +1 -0
- package/dist/{snapshot-zznDcPYo.js → snapshot-Bw7Q6-aV.js} +22 -9
- package/dist/snapshot-Bw7Q6-aV.js.map +1 -0
- package/dist/studioWorker.js +2 -2
- package/package.json +7 -7
- package/dist/Telemetry-BmdDiGPs.js.map +0 -1
- package/dist/Telemetry-DuLxs5c3.cjs.map +0 -1
- package/dist/background-CK9olfbv.cjs.map +0 -1
- package/dist/background-DWasKvSw.js.map +0 -1
- package/dist/config-BPuFepc5.js.map +0 -1
- package/dist/config-CGUQ39wR.cjs.map +0 -1
- package/dist/output-Ck7iT223.cjs.map +0 -1
- package/dist/output-DIJsbXzW.js.map +0 -1
- package/dist/registry-CHJCPOAR.js.map +0 -1
- package/dist/registry-gERiCBje.cjs.map +0 -1
- package/dist/run-B33l-Tx7.js.map +0 -1
- package/dist/run-Bqy3HW6o.cjs.map +0 -1
- package/dist/run-C3v9G5i3.cjs.map +0 -1
- package/dist/run-DNa_bKZE.js.map +0 -1
- package/dist/run-DORetnI8.js.map +0 -1
- package/dist/run-Ds-xI766.cjs.map +0 -1
- package/dist/snapshot-C4TMtjON.cjs.map +0 -1
- package/dist/snapshot-zznDcPYo.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-CPE0t6oh.cjs");
|
|
2
|
-
const require_Telemetry = require("./Telemetry-
|
|
3
|
-
const require_output = require("./output-
|
|
4
|
-
const require_config = require("./config-
|
|
2
|
+
const require_Telemetry = require("./Telemetry-DOahDJXF.cjs");
|
|
3
|
+
const require_output = require("./output-C3zLt8M9.cjs");
|
|
4
|
+
const require_config = require("./config-bF-Lo5C2.cjs");
|
|
5
5
|
const require_credentials = require("./credentials-DDaKqtJn.cjs");
|
|
6
6
|
let node_util = require("node:util");
|
|
7
7
|
let node_os = require("node:os");
|
|
@@ -10,10 +10,11 @@ node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
|
10
10
|
let tinyexec = require("tinyexec");
|
|
11
11
|
let node_path = require("node:path");
|
|
12
12
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
13
|
+
let _kubb_core = require("@kubb/core");
|
|
13
14
|
let _clack_prompts = require("@clack/prompts");
|
|
14
15
|
_clack_prompts = require_rolldown_runtime.__toESM(_clack_prompts, 1);
|
|
15
|
-
let _kubb_core = require("@kubb/core");
|
|
16
16
|
let _kubb_studio = require("@kubb/studio");
|
|
17
|
+
let _kubb_studio_protocol = require("@kubb/studio/protocol");
|
|
17
18
|
//#region ../../internals/utils/src/openInBrowser.ts
|
|
18
19
|
const ignore = () => {};
|
|
19
20
|
/**
|
|
@@ -82,27 +83,27 @@ async function login({ studioUrl, autoOpen }, { signal, previousCredentials } =
|
|
|
82
83
|
*/
|
|
83
84
|
const PERMISSIONS = [
|
|
84
85
|
{
|
|
85
|
-
key:
|
|
86
|
+
key: _kubb_studio_protocol.AgentPermission.READ,
|
|
86
87
|
label: "read generated files",
|
|
87
88
|
question: () => "Let Kubb Studio read the files a generation produced?"
|
|
88
89
|
},
|
|
89
90
|
{
|
|
90
|
-
key:
|
|
91
|
+
key: _kubb_studio_protocol.AgentPermission.READ_AST,
|
|
91
92
|
label: "read parsed spec",
|
|
92
93
|
question: () => "Let Kubb Studio read the parsed spec (the Kubb AST)?"
|
|
93
94
|
},
|
|
94
95
|
{
|
|
95
|
-
key:
|
|
96
|
+
key: _kubb_studio_protocol.AgentPermission.WRITE,
|
|
96
97
|
label: "write generated files",
|
|
97
98
|
question: (project) => `Let Kubb Studio write generated files into ${project}?`
|
|
98
99
|
},
|
|
99
100
|
{
|
|
100
|
-
key:
|
|
101
|
+
key: _kubb_studio_protocol.AgentPermission.CONFIG_EDIT,
|
|
101
102
|
label: "edit kubb.config.ts",
|
|
102
103
|
question: (_project, configPath) => `Let Kubb Studio change plugin options in ${configPath}?`
|
|
103
104
|
},
|
|
104
105
|
{
|
|
105
|
-
key:
|
|
106
|
+
key: _kubb_studio_protocol.AgentPermission.EXEC,
|
|
106
107
|
label: "run formatter, linter, postGenerate",
|
|
107
108
|
question: () => "Let Kubb Studio run the formatter, the linter, and output.postGenerate?"
|
|
108
109
|
}
|
|
@@ -167,7 +168,11 @@ async function loadConfigs(options) {
|
|
|
167
168
|
logLevel: options.logLevel
|
|
168
169
|
});
|
|
169
170
|
const [config] = configs;
|
|
170
|
-
if (!config) throw new Error(
|
|
171
|
+
if (!config) throw new _kubb_core.Diagnostics.Error({
|
|
172
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
173
|
+
severity: "error",
|
|
174
|
+
message: "Config not defined, create a kubb.config.ts or pass it with --config"
|
|
175
|
+
});
|
|
171
176
|
return {
|
|
172
177
|
configPath: node_path.default.relative(node_process.default.cwd(), configPath) || configPath,
|
|
173
178
|
config
|
|
@@ -187,7 +192,11 @@ async function prepareConnection(options, signal) {
|
|
|
187
192
|
const stored = await require_credentials.readCredentials();
|
|
188
193
|
const credentials = await (async () => {
|
|
189
194
|
if (stored?.studioUrl === options.studioUrl) return stored;
|
|
190
|
-
if (require_Telemetry.isCIEnvironment()) throw new Error(
|
|
195
|
+
if (require_Telemetry.isCIEnvironment()) throw new _kubb_core.Diagnostics.Error({
|
|
196
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
197
|
+
severity: "error",
|
|
198
|
+
message: `Not paired with ${options.studioUrl}. In CI, run \`kubb studio snapshot\` instead, or run \`kubb studio login\` on a machine with a browser.`
|
|
199
|
+
});
|
|
191
200
|
return login(options, { signal });
|
|
192
201
|
})();
|
|
193
202
|
return {
|
|
@@ -208,8 +217,12 @@ async function connect(options, context = {}) {
|
|
|
208
217
|
}
|
|
209
218
|
try {
|
|
210
219
|
if (!context.prepared) {
|
|
211
|
-
const { getWorkerStatus } = await Promise.resolve().then(() => require("./background-
|
|
212
|
-
if ((await getWorkerStatus()).running) throw new Error(
|
|
220
|
+
const { getWorkerStatus } = await Promise.resolve().then(() => require("./background-CF80MwuR.cjs"));
|
|
221
|
+
if ((await getWorkerStatus()).running) throw new _kubb_core.Diagnostics.Error({
|
|
222
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
223
|
+
severity: "error",
|
|
224
|
+
message: "This project is running in the background. Run `kubb studio stop` first."
|
|
225
|
+
});
|
|
213
226
|
}
|
|
214
227
|
const prepared = context.prepared ?? await prepareConnection(options, signal);
|
|
215
228
|
let { credentials, permissions } = prepared;
|
|
@@ -310,4 +323,4 @@ exports.login = login;
|
|
|
310
323
|
exports.prepareConnection = prepareConnection;
|
|
311
324
|
exports.runStudio = runStudio;
|
|
312
325
|
|
|
313
|
-
//# sourceMappingURL=run-
|
|
326
|
+
//# sourceMappingURL=run-xXgz6PA_.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-xXgz6PA_.cjs","names":["isCIEnvironment","process","createSpinner","pairAgent","path","process","hostname","getMachineId","styleText","writeCredentials","getCredentialsPath","PairingCanceledError","AgentPermission","KUBB_CONFIG_FILENAME","isCIEnvironment","canUseTTY","prompts","getConfigs","Diagnostics","readCredentials","Hookable","logLevelMap","cliReporter","plainLogger","connectAgent","InvalidAgentTokenError","clearCredentials","defaultStudioUrl","trackRun","toError"],"sources":["../../../internals/utils/src/openInBrowser.ts","../src/runners/studio/run.ts"],"sourcesContent":["import process from 'node:process'\nimport { x } from 'tinyexec'\nimport { isCIEnvironment } from './isCIEnvironment.ts'\n\nconst ignore = () => {}\n\n/**\n * Opens a URL or local file in the default browser, unless running in CI.\n */\nexport function openInBrowser(target: string): void {\n if (isCIEnvironment()) return\n\n if (process.platform === 'win32') {\n x('cmd', ['/c', 'start', '', target]).then(ignore, ignore)\n return\n }\n x(process.platform === 'darwin' ? 'open' : 'xdg-open', [target]).then(ignore, ignore)\n}\n","import { hostname } from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport * as prompts from '@clack/prompts'\nimport { KUBB_CONFIG_FILENAME } from '@internals/shared'\nimport { isCIEnvironment, openInBrowser, toError } from '@internals/utils'\nimport type { CLIOptions, Config } from '@kubb/core'\nimport { cliReporter, Diagnostics, Hookable, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'\nimport { connectAgent, defaultStudioUrl, InvalidAgentTokenError, pairAgent, PairingCanceledError } from '@kubb/studio'\nimport { AgentPermission } from '@kubb/studio/protocol'\nimport { trackRun } from '../../Telemetry.ts'\nimport { plainLogger } from '../../loggers/plainLogger.ts'\nimport setupReporters from '../../loggers/reporters.ts'\nimport { createSpinner, logBlock, logIntro, logOutro, logTip } from '../../loggers/output.ts'\nimport { canUseTTY } from '../../utils/env.ts'\nimport { getConfigs } from '../../config.ts'\nimport { clearCredentials, type Credentials, getCredentialsPath, getMachineId, readCredentials, writeCredentials } from './credentials.ts'\nimport { version } from '../../../package.json'\n\nexport type StudioOptions = {\n /**\n * Current `@kubb/cli` version, reported to Studio and used for the telemetry payload.\n */\n version: string\n configPath?: string\n /**\n * Base URL of the Studio instance, for a self-hosted deployment. Resolved before it reaches here,\n * since stored credentials are bound to it.\n */\n studioUrl: string\n /**\n * What Studio may do in this project. Each flag grants outright; the rest are asked once per\n * project through {@link resolvePermissions}.\n */\n permission: Record<AgentPermission, boolean>\n /**\n * Whether to open the approval page in a browser during pairing.\n */\n autoOpen: boolean\n logLevel?: CLIOptions['logLevel']\n}\n\nexport type SnapshotOptions = Pick<StudioOptions, 'configPath' | 'studioUrl' | 'logLevel'> & {\n /**\n * Organization CI API key. Falls back to `KUBB_TOKEN`.\n */\n token?: string\n /**\n * Stable identity for the CI agent. Falls back to CI auto-detection.\n */\n id?: string\n /** Identity of another CI agent to also compare with. Falls back to CI auto-detection. */\n baseId?: string\n /**\n * Package name for the generated tarball. Falls back to the nearest package.json.\n */\n name?: string\n /**\n * Package version for the generated tarball. Falls back to the nearest package.json.\n */\n packageVersion?: string\n /**\n * Print the result as one JSON object instead of a summary.\n */\n json?: boolean\n}\n\n/** The flags a `kubb studio` subcommand parsed; each one is optional since a subcommand declares only the flags it uses. */\nexport type StudioValues = {\n config?: string\n url?: string\n allowRead?: boolean\n allowReadAst?: boolean\n allowWrite?: boolean\n allowConfigEdit?: boolean\n allowExec?: boolean\n open?: boolean\n logLevel?: CLIOptions['logLevel']\n}\n\ntype LoginOptions = {\n /**\n * Aborting this cancels an in-flight pairing request or poll and rejects with\n * {@link PairingCanceledError}. Wired to `kubb studio`'s shutdown signal, so Ctrl+C during\n * pairing cancels it instead of leaving the poll running.\n */\n signal?: AbortSignal\n /**\n * Credentials from before this login. When the fresh pairing resolves to the same agent on the\n * same Studio, its saved project permissions carry forward instead of being asked again.\n * Left out for an explicitly requested `kubb studio login`, which always starts clean.\n */\n previousCredentials?: Credentials | null\n}\n\n/**\n * Pairs this machine with Studio and stores the resulting token.\n *\n * The CLI holds a code and the browser approves it, rather than the user copying a token out of\n * the UI. The token comes back over the CLI's own HTTPS POST, so it never lands in a URL, a\n * server log, or a `Referer` header.\n */\nexport async function login({ studioUrl, autoOpen }: StudioOptions, { signal, previousCredentials }: LoginOptions = {}): Promise<Credentials> {\n const spinner = createSpinner()\n // The spinner only starts once a code is shown.\n let waiting = false\n\n try {\n const { token, agent } = await pairAgent({\n studioUrl,\n type: 'cli',\n name: path.basename(process.cwd()),\n hostname: hostname(),\n machineId: await getMachineId(),\n signal,\n onCode: (session) => {\n console.log(`\\nOpen ${styleText('cyan', session.verification_uri)} and approve the code ${styleText('bold', session.user_code)}`)\n\n if (autoOpen) {\n openInBrowser(session.verification_uri_complete)\n }\n\n spinner.start('Waiting for approval')\n waiting = true\n },\n onRetry: (error) => spinner.message(`Could not reach Kubb Studio, retrying: ${error.message}`),\n })\n spinner.stop(`Paired as ${agent.name}`)\n\n const keepsIdentity = previousCredentials?.studioUrl === studioUrl && previousCredentials.agentId === agent.id\n const credentials: Credentials = {\n studioUrl,\n token,\n agentId: agent.id,\n agentSlug: agent.slug,\n ...(keepsIdentity && previousCredentials?.projects ? { projects: previousCredentials.projects } : {}),\n }\n await writeCredentials(credentials)\n\n console.log(`Credentials stored in ${getCredentialsPath()}`)\n\n return credentials\n } catch (error) {\n if (waiting) {\n spinner.stop(error instanceof PairingCanceledError ? 'Pairing canceled' : 'Pairing failed')\n }\n throw error\n }\n}\n\n/**\n * What each permission is asked as, in the order the questions appear.\n */\nconst PERMISSIONS: ReadonlyArray<{\n key: AgentPermission\n /**\n * Short label for status output and the connect summary.\n */\n label: string\n question: (project: string, configPath: string) => string\n}> = [\n {\n key: AgentPermission.READ,\n label: 'read generated files',\n question: () => 'Let Kubb Studio read the files a generation produced?',\n },\n { key: AgentPermission.READ_AST, label: 'read parsed spec', question: () => 'Let Kubb Studio read the parsed spec (the Kubb AST)?' },\n { key: AgentPermission.WRITE, label: 'write generated files', question: (project) => `Let Kubb Studio write generated files into ${project}?` },\n {\n key: AgentPermission.CONFIG_EDIT,\n label: 'edit kubb.config.ts',\n question: (_project, configPath) => `Let Kubb Studio change plugin options in ${configPath}?`,\n },\n {\n key: AgentPermission.EXEC,\n label: 'run formatter, linter, postGenerate',\n question: () => 'Let Kubb Studio run the formatter, the linter, and output.postGenerate?',\n },\n]\n\n/**\n * One row per permission, for the connect banner and `kubb studio status`. A list rather than a\n * joined line: four labels this long read as one run-on sentence side by side.\n */\nexport function formatPermissionRows(granted: Record<AgentPermission, boolean>): Array<string> {\n return PERMISSIONS.map(({ key, label }) => `${granted[key] ? styleText('green', '✔') : styleText('red', '✘')} ${label}`)\n}\n\n/**\n * Resolves Studio permissions for the current project.\n * Flags win, saved answers are reused, and new answers are stored in `~/.kubb/credentials.json`.\n */\nexport async function resolvePermissions(\n options: StudioOptions,\n credentials: Credentials,\n configPath: string = KUBB_CONFIG_FILENAME,\n): Promise<Record<AgentPermission, boolean>> {\n const project = process.cwd()\n const remembered = credentials.projects?.[project]\n const granted: Record<AgentPermission, boolean> = { allowRead: false, allowReadAst: false, allowWrite: false, allowConfigEdit: false, allowExec: false }\n const answers: Partial<Record<AgentPermission, boolean>> = {}\n prompts.updateSettings({ withGuide: true })\n\n for (const { key, question } of PERMISSIONS) {\n if (options.permission[key] || typeof remembered?.[key] === 'boolean') {\n granted[key] = options.permission[key] || remembered?.[key] === true\n continue\n }\n\n if (isCIEnvironment() || !canUseTTY()) {\n granted[key] = false\n continue\n }\n\n granted[key] = (await prompts.confirm({ message: question(project, configPath), initialValue: false })) === true\n answers[key] = granted[key]\n }\n if (Object.keys(answers).length) {\n await writeCredentials({\n ...credentials,\n projects: { ...credentials.projects, [project]: { ...remembered, ...answers } },\n })\n }\n\n return granted\n}\n\n/**\n * Loads the project's Kubb config the same way `kubb generate` does.\n * Returns the first config.\n */\nexport async function loadConfigs(options: Pick<StudioOptions, 'configPath' | 'logLevel'>): Promise<{ configPath: string; config: Config }> {\n const { configPath, configs } = await getConfigs({ configPath: options.configPath, logLevel: options.logLevel })\n const [config] = configs\n\n if (!config) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'Config not defined, create a kubb.config.ts or pass it with --config',\n })\n }\n\n // `getConfigs` resolves this to an absolute path. Relativized here, once, so the permission\n // prompt and the path Studio receives over the wire both show the project-relative form instead\n // of leaking the local filesystem layout.\n return { configPath: path.relative(process.cwd(), configPath) || configPath, config }\n}\n\n/**\n * Why a rejected token could not be recovered automatically: no browser is available to re-pair\n * with, or the token this run already re-paired for was rejected again.\n */\ntype RejectedTokenReason = 'nonInteractive' | 'reauthExhausted'\n\n/**\n * Explains a rejected token to the operator. Builds the error and nothing else, so each caller\n * decides what happens to the credentials.\n */\nfunction explainRejectedToken(error: InvalidAgentTokenError, reason: RejectedTokenReason): Error {\n if (reason === 'reauthExhausted') {\n return new Error(`${error.message} Studio rejected the newly approved token too. Run \\`kubb studio login\\` and try again.`)\n }\n\n return new Error(`${error.message} Run \\`kubb studio login\\` to pair again.`)\n}\n\nexport type PreparedConnection = {\n configPath: string\n credentials: Credentials\n permissions: StudioOptions['permission']\n}\n\n/** Validates the project and resolves pairing and permissions before starting its worker. */\nexport async function prepareConnection(options: StudioOptions, signal?: AbortSignal): Promise<PreparedConnection> {\n const { configPath } = await loadConfigs(options)\n const stored = await readCredentials()\n const credentials = await (async () => {\n if (stored?.studioUrl === options.studioUrl) return stored\n if (isCIEnvironment()) {\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: `Not paired with ${options.studioUrl}. In CI, run \\`kubb studio snapshot\\` instead, or run \\`kubb studio login\\` on a machine with a browser.`,\n })\n }\n return login(options, { signal })\n })()\n const permissions = await resolvePermissions(options, credentials, configPath)\n return { configPath, credentials, permissions }\n}\n\nexport type WorkerState = 'starting' | 'connected' | 'reconnecting' | 'authentication required' | 'stopped'\n\n/** Runs the same project connection in the foreground or in a background worker. */\nexport async function connect(\n options: StudioOptions,\n context: {\n prepared?: PreparedConnection\n signal?: AbortSignal\n onState?: (state: WorkerState) => void | Promise<void>\n } = {},\n): Promise<void> {\n const shutdown = new AbortController()\n const signal = context.signal ?? shutdown.signal\n const stop = () => shutdown.abort()\n const events = process as unknown as NodeJS.EventEmitter\n if (!context.signal) {\n events.once('SIGINT', stop)\n events.once('SIGTERM', stop)\n }\n try {\n if (!context.prepared) {\n const { getWorkerStatus } = await import('./background.ts')\n const worker = await getWorkerStatus()\n if (worker.running)\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'This project is running in the background. Run `kubb studio stop` first.',\n })\n }\n const prepared = context.prepared ?? (await prepareConnection(options, signal))\n let { credentials, permissions } = prepared\n const { configPath } = prepared\n let hinted = false\n // Set by a fresh pairing until it connects, so a token rejected right after approval fails the run instead of looping.\n let pairedAgain = false\n\n // The background worker writes to a log file, so it gets the plain logger rather than the\n // animated one. The logger also covers the `studio:*` events and each generation run.\n const hooks = new Hookable<KubbHooks>()\n setupReporters(hooks, {\n logLevel: logLevelMap[options.logLevel ?? 'info'],\n reporters: [cliReporter],\n ...(context.onState ? { logger: plainLogger } : {}),\n })\n hooks.hook('studio:connected', async () => {\n pairedAgain = false\n await context.onState?.('connected')\n if (hinted || context.onState || options.logLevel === 'silent') return\n hinted = true\n logBlock(styleText('dim', 'Press Ctrl+C to disconnect'))\n logTip()\n })\n hooks.hook('studio:reconnecting', () => context.onState?.('reconnecting'))\n\n for (;;) {\n try {\n await connectAgent({\n token: credentials.token,\n studioUrl: options.studioUrl,\n configPath,\n version: options.version,\n root: process.cwd(),\n permissions,\n loadConfig: async () => (await loadConfigs(options)).config,\n hooks,\n signal,\n })\n break\n } catch (error) {\n if (!(error instanceof InvalidAgentTokenError)) throw error\n await context.onState?.('authentication required')\n await clearCredentials()\n if (pairedAgain) throw explainRejectedToken(error, 'reauthExhausted')\n if (context.onState || isCIEnvironment() || !canUseTTY()) throw explainRejectedToken(error, 'nonInteractive')\n\n console.log(styleText('yellow', `${error.message} Pairing again...`))\n credentials = await login(options, { signal, previousCredentials: credentials })\n pairedAgain = true\n permissions = await resolvePermissions(options, credentials, configPath)\n }\n }\n if (!context.onState && signal.aborted && options.logLevel !== 'silent') logOutro('Disconnected')\n } catch (error) {\n if (!(error instanceof PairingCanceledError)) throw error\n } finally {\n events.off('SIGINT', stop)\n events.off('SIGTERM', stop)\n }\n}\n\n/**\n * Runs a Studio command from its parsed flags: builds the {@link StudioOptions}, prints the intro,\n * and reports the run to telemetry. A failure is printed and sets the exit code instead of throwing.\n */\nexport async function runStudio(\n values: StudioValues,\n action: (options: StudioOptions) => Promise<unknown>,\n { block = false, json = false }: { block?: boolean; json?: boolean } = {},\n): Promise<void> {\n const options: StudioOptions = {\n version,\n configPath: values.config,\n studioUrl: values.url ?? defaultStudioUrl,\n permission: {\n allowRead: values.allowRead ?? false,\n allowReadAst: values.allowReadAst ?? false,\n allowWrite: values.allowWrite ?? false,\n allowConfigEdit: values.allowConfigEdit ?? false,\n allowExec: values.allowExec ?? false,\n },\n autoOpen: values.open ?? true,\n logLevel: values.logLevel,\n }\n const report = trackRun({ command: 'studio', hrStart: process.hrtime() })\n\n try {\n if (options.logLevel !== 'silent' && !json) {\n logIntro({\n warning: styleText('yellow', 'This feature is still under development, use with caution'),\n block,\n })\n }\n\n await action(options)\n\n await report({ status: 'success' })\n } catch (error) {\n await report({ status: 'failed' })\n console.error(toError(error).message)\n process.exitCode = 1\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-CPE0t6oh.cjs");
|
|
2
|
-
const require_Telemetry = require("./Telemetry-
|
|
3
|
-
const require_output = require("./output-
|
|
4
|
-
const require_config = require("./config-
|
|
5
|
-
const require_run = require("./run-
|
|
2
|
+
const require_Telemetry = require("./Telemetry-DOahDJXF.cjs");
|
|
3
|
+
const require_output = require("./output-C3zLt8M9.cjs");
|
|
4
|
+
const require_config = require("./config-bF-Lo5C2.cjs");
|
|
5
|
+
const require_run = require("./run-xXgz6PA_.cjs");
|
|
6
6
|
let node_util = require("node:util");
|
|
7
7
|
let node_process = require("node:process");
|
|
8
8
|
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
10
10
|
let node_path = require("node:path");
|
|
11
|
-
let node_fs = require("node:fs");
|
|
12
11
|
let _kubb_core = require("@kubb/core");
|
|
12
|
+
let node_fs = require("node:fs");
|
|
13
13
|
let _kubb_studio = require("@kubb/studio");
|
|
14
14
|
//#region ../../internals/utils/src/fs.ts
|
|
15
15
|
/**
|
|
@@ -107,7 +107,12 @@ async function findPackageJson(directory) {
|
|
|
107
107
|
version: packageJson.version
|
|
108
108
|
};
|
|
109
109
|
const parent = (0, node_path.dirname)(directory);
|
|
110
|
-
if (parent === directory) throw new Error(
|
|
110
|
+
if (parent === directory) throw new _kubb_core.Diagnostics.Error({
|
|
111
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
112
|
+
severity: "error",
|
|
113
|
+
message: `No package.json with a name and a version found above ${node_process.default.cwd()}`,
|
|
114
|
+
help: "Pass `--name` and `--version`, or run from a package with a package.json."
|
|
115
|
+
});
|
|
111
116
|
return findPackageJson(parent);
|
|
112
117
|
}
|
|
113
118
|
/** Reads package.json only for whichever of name/version --name and --version did not supply. */
|
|
@@ -124,7 +129,11 @@ async function resolvePackageMetadata(options) {
|
|
|
124
129
|
}
|
|
125
130
|
function resolveToken(options) {
|
|
126
131
|
const token = options.token ?? node_process.default.env.KUBB_TOKEN;
|
|
127
|
-
if (!token) throw new Error(
|
|
132
|
+
if (!token) throw new _kubb_core.Diagnostics.Error({
|
|
133
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
134
|
+
severity: "error",
|
|
135
|
+
message: "An organization CI API key is required. Pass --token or set KUBB_TOKEN."
|
|
136
|
+
});
|
|
128
137
|
return token;
|
|
129
138
|
}
|
|
130
139
|
function resolveCiIdentity(options) {
|
|
@@ -134,7 +143,11 @@ function resolveCiIdentity(options) {
|
|
|
134
143
|
name: detected?.name ?? options.id,
|
|
135
144
|
commit: detected?.commit
|
|
136
145
|
} : detected;
|
|
137
|
-
if (!ci) throw new Error(
|
|
146
|
+
if (!ci) throw new _kubb_core.Diagnostics.Error({
|
|
147
|
+
code: _kubb_core.Diagnostics.code.unknown,
|
|
148
|
+
severity: "error",
|
|
149
|
+
message: "Could not detect a supported CI provider (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI). Pass --id."
|
|
150
|
+
});
|
|
138
151
|
const base = options.baseId ? {
|
|
139
152
|
branch: options.baseId,
|
|
140
153
|
id: options.baseId
|
|
@@ -248,4 +261,4 @@ const runner = ({ values }) => require_run.runStudio(values, (options) => snapsh
|
|
|
248
261
|
//#endregion
|
|
249
262
|
exports.runner = runner;
|
|
250
263
|
|
|
251
|
-
//# sourceMappingURL=snapshot-
|
|
264
|
+
//# sourceMappingURL=snapshot-Bk0S2Myh.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-Bk0S2Myh.cjs","names":["runtime","readFile","readFileSync","process","join","dirname","Diagnostics","process","styleText","loadConfigs","logLevelMap","Hookable","cliReporter","createPlainLogger","createSnapshot","runStudio"],"sources":["../../../internals/utils/src/fs.ts","../src/runners/studio/ci.ts","../src/runners/studio/snapshot.ts"],"sourcesContent":["import { access, mkdir, readFile, 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 * 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, Diagnostics, Hookable, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'\nimport { createSnapshot } 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, runStudio, 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)\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: `No package.json with a name and a version found above ${process.cwd()}`,\n help: 'Pass `--name` and `--version`, or run from a package with a package.json.',\n })\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 Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'An organization CI API key is required. Pass --token or set KUBB_TOKEN.',\n })\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 Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'Could not detect a supported CI provider (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI). Pass --id.',\n })\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: {} }> = ({ values }) =>\n runStudio(\n values,\n (options) =>\n snapshot({\n ...options,\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 { json: values.json },\n )\n"],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
-
import { o as runtime } from "./Telemetry-
|
|
3
|
-
import { r as loadConfigs, s as runStudio } from "./run-
|
|
4
|
-
import { f as createPlainLogger, r as logBlock } from "./output-
|
|
5
|
-
import { r as setupReporters } from "./config-
|
|
2
|
+
import { o as runtime } from "./Telemetry-4smY7ClN.js";
|
|
3
|
+
import { r as loadConfigs, s as runStudio } from "./run-DsRPwmkb.js";
|
|
4
|
+
import { f as createPlainLogger, r as logBlock } from "./output-CxcaLxy9.js";
|
|
5
|
+
import { r as setupReporters } from "./config-BC4Wm0OK.js";
|
|
6
6
|
import { styleText } from "node:util";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { readFile } from "node:fs/promises";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
|
+
import { Diagnostics, Hookable, cliReporter, logLevel } from "@kubb/core";
|
|
10
11
|
import { readFileSync } from "node:fs";
|
|
11
|
-
import { Hookable, cliReporter, logLevel } from "@kubb/core";
|
|
12
12
|
import { createSnapshot } from "@kubb/studio";
|
|
13
13
|
//#region ../../internals/utils/src/fs.ts
|
|
14
14
|
/**
|
|
@@ -106,7 +106,12 @@ async function findPackageJson(directory) {
|
|
|
106
106
|
version: packageJson.version
|
|
107
107
|
};
|
|
108
108
|
const parent = dirname(directory);
|
|
109
|
-
if (parent === directory) throw new Error(
|
|
109
|
+
if (parent === directory) throw new Diagnostics.Error({
|
|
110
|
+
code: Diagnostics.code.unknown,
|
|
111
|
+
severity: "error",
|
|
112
|
+
message: `No package.json with a name and a version found above ${process.cwd()}`,
|
|
113
|
+
help: "Pass `--name` and `--version`, or run from a package with a package.json."
|
|
114
|
+
});
|
|
110
115
|
return findPackageJson(parent);
|
|
111
116
|
}
|
|
112
117
|
/** Reads package.json only for whichever of name/version --name and --version did not supply. */
|
|
@@ -123,7 +128,11 @@ async function resolvePackageMetadata(options) {
|
|
|
123
128
|
}
|
|
124
129
|
function resolveToken(options) {
|
|
125
130
|
const token = options.token ?? process.env.KUBB_TOKEN;
|
|
126
|
-
if (!token) throw new Error(
|
|
131
|
+
if (!token) throw new Diagnostics.Error({
|
|
132
|
+
code: Diagnostics.code.unknown,
|
|
133
|
+
severity: "error",
|
|
134
|
+
message: "An organization CI API key is required. Pass --token or set KUBB_TOKEN."
|
|
135
|
+
});
|
|
127
136
|
return token;
|
|
128
137
|
}
|
|
129
138
|
function resolveCiIdentity(options) {
|
|
@@ -133,7 +142,11 @@ function resolveCiIdentity(options) {
|
|
|
133
142
|
name: detected?.name ?? options.id,
|
|
134
143
|
commit: detected?.commit
|
|
135
144
|
} : detected;
|
|
136
|
-
if (!ci) throw new Error(
|
|
145
|
+
if (!ci) throw new Diagnostics.Error({
|
|
146
|
+
code: Diagnostics.code.unknown,
|
|
147
|
+
severity: "error",
|
|
148
|
+
message: "Could not detect a supported CI provider (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI). Pass --id."
|
|
149
|
+
});
|
|
137
150
|
const base = options.baseId ? {
|
|
138
151
|
branch: options.baseId,
|
|
139
152
|
id: options.baseId
|
|
@@ -247,4 +260,4 @@ const runner = ({ values }) => runStudio(values, (options) => snapshot({
|
|
|
247
260
|
//#endregion
|
|
248
261
|
export { runner };
|
|
249
262
|
|
|
250
|
-
//# sourceMappingURL=snapshot-
|
|
263
|
+
//# sourceMappingURL=snapshot-Bw7Q6-aV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-Bw7Q6-aV.js","names":["logLevel","logLevelMap"],"sources":["../../../internals/utils/src/fs.ts","../src/runners/studio/ci.ts","../src/runners/studio/snapshot.ts"],"sourcesContent":["import { access, mkdir, readFile, 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 * 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, Diagnostics, Hookable, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'\nimport { createSnapshot } 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, runStudio, 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)\n throw new Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: `No package.json with a name and a version found above ${process.cwd()}`,\n help: 'Pass `--name` and `--version`, or run from a package with a package.json.',\n })\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 Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'An organization CI API key is required. Pass --token or set KUBB_TOKEN.',\n })\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 Diagnostics.Error({\n code: Diagnostics.code.unknown,\n severity: 'error',\n message: 'Could not detect a supported CI provider (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI). Pass --id.',\n })\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: {} }> = ({ values }) =>\n runStudio(\n values,\n (options) =>\n snapshot({\n ...options,\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 { json: values.json },\n )\n"],"mappings":"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"}
|
package/dist/studioWorker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
-
import { t as connect } from "./run-
|
|
3
|
-
import { r as serveWorker, t as getWorkerLogPath } from "./background-
|
|
2
|
+
import { t as connect } from "./run-DsRPwmkb.js";
|
|
3
|
+
import { r as serveWorker, t as getWorkerLogPath } from "./background-BKWslHZJ.js";
|
|
4
4
|
import { format } from "node:util";
|
|
5
5
|
import { appendFileSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
//#region src/runners/studio/worker.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/cli",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.2",
|
|
4
4
|
"description": "Official CLI for Kubb. Run kubb generate, kubb init, kubb validate, kubb studio, and kubb mcp to manage the full code generation lifecycle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@clack/prompts": "^1.8.1",
|
|
65
65
|
"@gunshi/plugin-dryrun": "^0.37.3",
|
|
66
|
-
"@kubb/core": "5.10.
|
|
66
|
+
"@kubb/core": "5.10.2",
|
|
67
67
|
"chokidar": "^5.0.0",
|
|
68
68
|
"gunshi": "^0.37.3",
|
|
69
69
|
"jiti": "^2.7.0",
|
|
@@ -73,13 +73,13 @@
|
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@internals/shared": "0.0.1",
|
|
75
75
|
"@internals/utils": "0.0.1",
|
|
76
|
-
"@kubb/adapter-oas": "5.10.
|
|
77
|
-
"@kubb/studio": "5.10.
|
|
76
|
+
"@kubb/adapter-oas": "5.10.2",
|
|
77
|
+
"@kubb/studio": "5.10.2"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@kubb/adapter-oas": "5.10.
|
|
81
|
-
"@kubb/mcp": "5.10.
|
|
82
|
-
"@kubb/studio": "5.10.
|
|
80
|
+
"@kubb/adapter-oas": "5.10.2",
|
|
81
|
+
"@kubb/mcp": "5.10.2",
|
|
82
|
+
"@kubb/studio": "5.10.2"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
85
85
|
"@kubb/adapter-oas": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Telemetry-BmdDiGPs.js","names":["process","process"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/isCIEnvironment.ts","../../../internals/utils/src/runtime.ts","../../../internals/utils/src/time.ts","../package.json","../src/Telemetry.ts"],"sourcesContent":["/**\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","import process from 'node:process'\n\n/**\n * Returns `true` when a known CI provider has set its environment variable.\n * `CI=false` and `CI=0` count as not CI, so a user can opt out locally.\n */\nexport function isCIEnvironment(): boolean {\n const ci = process.env.CI\n return !!(\n (ci && ci !== 'false' && ci !== '0') ||\n process.env.GITHUB_ACTIONS ||\n process.env.GITLAB_CI ||\n process.env.BITBUCKET_BUILD_NUMBER ||\n process.env.JENKINS_URL ||\n process.env.CIRCLECI ||\n process.env.TRAVIS ||\n process.env.TEAMCITY_VERSION ||\n process.env.BUILDKITE ||\n process.env.TF_BUILD\n )\n}\n","// Bun polyfills `process.versions.node`, so detection keys off the globals instead.\nconst isBun = typeof Bun !== 'undefined'\nconst isDeno = 'Deno' in globalThis\n\n/**\n * The JavaScript runtime executing the current process: its name, version and whether it is Bun.\n *\n * @example\n * ```ts\n * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise\n * ```\n */\nexport const runtime = { isBun, ...detect() } as const\n\nfunction detect(): { name: 'bun' | 'deno' | 'node'; version: string } {\n if (isBun) return { name: 'bun', version: process.versions.bun ?? '' }\n if (isDeno) return { name: 'deno', version: (globalThis as { Deno?: { version?: { deno?: string } } }).Deno?.version?.deno ?? '' }\n return { name: 'node', version: process.versions?.node ?? '' }\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 { randomBytes } from 'node:crypto'\nimport os from 'node:os'\nimport process from 'node:process'\nimport { getElapsedMs, isCIEnvironment, runtime } from '@internals/utils'\nimport { getAgentProfile } from 'gunshi/agent'\nimport { version } from '../package.json'\n\n/** OpenTelemetry ingestion endpoint for anonymous usage telemetry. */\nconst OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const\n\ntype TelemetryCommand = 'generate' | 'mcp' | 'studio' | 'validate'\n\n/**\n * Anonymous plugin name and options snapshot sent with each telemetry event.\n */\nexport type TelemetryPlugin = {\n /**\n * Plugin name as registered in the Kubb config, e.g. `'@kubb/plugin-ts'`.\n */\n name: string\n /**\n * Anonymized snapshot of the plugin options. Values are included but cannot be traced back to a user.\n */\n options: Record<string, unknown>\n}\n\n/** How a command run ended, as the command reports it to the function {@link trackRun} returned. */\ntype RunResult = {\n status: 'success' | 'failed'\n plugins?: Array<TelemetryPlugin>\n filesCreated?: number\n}\n\n/**\n * Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.\n */\nexport function isDisabled(): boolean {\n return ['DO_NOT_TRACK', 'KUBB_DISABLE_TELEMETRY'].some((name) => ['1', 'true'].includes(process.env[name] ?? ''))\n}\n\n/** Tracks one command run for the running CLI version; returns the reporter the command calls once it knows how it ended. */\nexport function trackRun({ command, hrStart }: { command: TelemetryCommand; hrStart: [number, number] }): (result: RunResult) => Promise<void> {\n return (result) => sendTelemetry(buildOtlpPayload({ command, duration: Math.round(getElapsedMs(hrStart)), ...result }))\n}\n\n/**\n * The OTLP trace payload for one run: a span named after the command whose attributes are the\n * anonymous run facts. The attribute keys are the contract with otlp.kubb.dev.\n *\n * @see https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/\n */\nexport function buildOtlpPayload({ command, duration, status, plugins = [], filesCreated = 0 }: RunResult & { command: TelemetryCommand; duration: number }) {\n const traceId = randomBytes(16).toString('hex')\n const spanId = randomBytes(8).toString('hex')\n const endTimeNs = BigInt(Date.now()) * 1_000_000n\n const startTimeNs = endTimeNs - BigInt(duration) * 1_000_000n\n const pluginNames = [...new Set(plugins.map((plugin) => plugin.name))].sort()\n const agent = getAgentProfile()\n\n const attributes = [\n { key: 'kubb.command', value: { stringValue: command } },\n { key: 'kubb.version', value: { stringValue: version } },\n { key: 'kubb.node_version', value: { stringValue: process.versions.node.split('.')[0] as string } },\n { key: 'kubb.runtime', value: { stringValue: runtime.name } },\n { key: 'kubb.runtime_version', value: { stringValue: runtime.version.split('.')[0] as string } },\n { key: 'kubb.platform', value: { stringValue: os.platform() } },\n { key: 'kubb.ci', value: { boolValue: isCIEnvironment() } },\n ...(agent.isAgent && agent.name ? [{ key: 'kubb.agent', value: { stringValue: agent.name } }] : []),\n { key: 'kubb.files_created', value: { intValue: filesCreated } },\n { key: 'kubb.status', value: { stringValue: status } },\n {\n key: 'kubb.plugins',\n value: { arrayValue: { values: pluginNames.map((name) => ({ stringValue: name })) } },\n },\n ...pluginNames.map((name) => ({ key: `kubb.plugin.${name}`, value: { boolValue: true } })),\n {\n key: 'kubb.plugin_options',\n value: {\n arrayValue: {\n values: plugins.map((p) => ({\n kvlistValue: {\n values: [\n { key: 'name', value: { stringValue: p.name } },\n {\n key: 'options',\n value: {\n stringValue: JSON.stringify({\n ...p.options,\n usedEnumNames: undefined,\n }),\n },\n },\n ],\n },\n })),\n },\n },\n },\n ]\n\n return {\n resourceSpans: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: 'kubb' } },\n { key: 'service.version', value: { stringValue: version } },\n { key: 'telemetry.sdk.language', value: { stringValue: 'nodejs' } },\n ],\n },\n scopeSpans: [\n {\n scope: { name: 'kubb-core', version },\n spans: [\n {\n traceId,\n spanId,\n name: command,\n kind: 1,\n startTimeUnixNano: String(startTimeNs),\n endTimeUnixNano: String(endTimeNs),\n attributes,\n status: {\n code: status === 'success' ? 1 : 2,\n },\n },\n ],\n },\n ],\n },\n ],\n }\n}\n\n/**\n * Send an OTLP payload to the Kubb endpoint. Respects `DO_NOT_TRACK` and\n * `KUBB_DISABLE_TELEMETRY`, and fails silently so telemetry never interrupts a run. No file\n * paths, OpenAPI specs, or secrets are sent.\n */\nexport async function sendTelemetry(payload: ReturnType<typeof buildOtlpPayload>): Promise<void> {\n if (isDisabled()) {\n return\n }\n\n try {\n await fetch(`${OTLP_ENDPOINT}/v1/traces`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Kubb-Telemetry-Version': '1',\n 'Kubb-Telemetry-Source': 'kubb-cli',\n },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(5_000),\n })\n } catch (_e) {\n // Fail silently, telemetry must never break the run\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Telemetry-DuLxs5c3.cjs","names":["process","process","randomBytes","getAgentProfile","os"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/isCIEnvironment.ts","../../../internals/utils/src/runtime.ts","../../../internals/utils/src/time.ts","../package.json","../src/Telemetry.ts"],"sourcesContent":["/**\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","import process from 'node:process'\n\n/**\n * Returns `true` when a known CI provider has set its environment variable.\n * `CI=false` and `CI=0` count as not CI, so a user can opt out locally.\n */\nexport function isCIEnvironment(): boolean {\n const ci = process.env.CI\n return !!(\n (ci && ci !== 'false' && ci !== '0') ||\n process.env.GITHUB_ACTIONS ||\n process.env.GITLAB_CI ||\n process.env.BITBUCKET_BUILD_NUMBER ||\n process.env.JENKINS_URL ||\n process.env.CIRCLECI ||\n process.env.TRAVIS ||\n process.env.TEAMCITY_VERSION ||\n process.env.BUILDKITE ||\n process.env.TF_BUILD\n )\n}\n","// Bun polyfills `process.versions.node`, so detection keys off the globals instead.\nconst isBun = typeof Bun !== 'undefined'\nconst isDeno = 'Deno' in globalThis\n\n/**\n * The JavaScript runtime executing the current process: its name, version and whether it is Bun.\n *\n * @example\n * ```ts\n * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise\n * ```\n */\nexport const runtime = { isBun, ...detect() } as const\n\nfunction detect(): { name: 'bun' | 'deno' | 'node'; version: string } {\n if (isBun) return { name: 'bun', version: process.versions.bun ?? '' }\n if (isDeno) return { name: 'deno', version: (globalThis as { Deno?: { version?: { deno?: string } } }).Deno?.version?.deno ?? '' }\n return { name: 'node', version: process.versions?.node ?? '' }\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 { randomBytes } from 'node:crypto'\nimport os from 'node:os'\nimport process from 'node:process'\nimport { getElapsedMs, isCIEnvironment, runtime } from '@internals/utils'\nimport { getAgentProfile } from 'gunshi/agent'\nimport { version } from '../package.json'\n\n/** OpenTelemetry ingestion endpoint for anonymous usage telemetry. */\nconst OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const\n\ntype TelemetryCommand = 'generate' | 'mcp' | 'studio' | 'validate'\n\n/**\n * Anonymous plugin name and options snapshot sent with each telemetry event.\n */\nexport type TelemetryPlugin = {\n /**\n * Plugin name as registered in the Kubb config, e.g. `'@kubb/plugin-ts'`.\n */\n name: string\n /**\n * Anonymized snapshot of the plugin options. Values are included but cannot be traced back to a user.\n */\n options: Record<string, unknown>\n}\n\n/** How a command run ended, as the command reports it to the function {@link trackRun} returned. */\ntype RunResult = {\n status: 'success' | 'failed'\n plugins?: Array<TelemetryPlugin>\n filesCreated?: number\n}\n\n/**\n * Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.\n */\nexport function isDisabled(): boolean {\n return ['DO_NOT_TRACK', 'KUBB_DISABLE_TELEMETRY'].some((name) => ['1', 'true'].includes(process.env[name] ?? ''))\n}\n\n/** Tracks one command run for the running CLI version; returns the reporter the command calls once it knows how it ended. */\nexport function trackRun({ command, hrStart }: { command: TelemetryCommand; hrStart: [number, number] }): (result: RunResult) => Promise<void> {\n return (result) => sendTelemetry(buildOtlpPayload({ command, duration: Math.round(getElapsedMs(hrStart)), ...result }))\n}\n\n/**\n * The OTLP trace payload for one run: a span named after the command whose attributes are the\n * anonymous run facts. The attribute keys are the contract with otlp.kubb.dev.\n *\n * @see https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/\n */\nexport function buildOtlpPayload({ command, duration, status, plugins = [], filesCreated = 0 }: RunResult & { command: TelemetryCommand; duration: number }) {\n const traceId = randomBytes(16).toString('hex')\n const spanId = randomBytes(8).toString('hex')\n const endTimeNs = BigInt(Date.now()) * 1_000_000n\n const startTimeNs = endTimeNs - BigInt(duration) * 1_000_000n\n const pluginNames = [...new Set(plugins.map((plugin) => plugin.name))].sort()\n const agent = getAgentProfile()\n\n const attributes = [\n { key: 'kubb.command', value: { stringValue: command } },\n { key: 'kubb.version', value: { stringValue: version } },\n { key: 'kubb.node_version', value: { stringValue: process.versions.node.split('.')[0] as string } },\n { key: 'kubb.runtime', value: { stringValue: runtime.name } },\n { key: 'kubb.runtime_version', value: { stringValue: runtime.version.split('.')[0] as string } },\n { key: 'kubb.platform', value: { stringValue: os.platform() } },\n { key: 'kubb.ci', value: { boolValue: isCIEnvironment() } },\n ...(agent.isAgent && agent.name ? [{ key: 'kubb.agent', value: { stringValue: agent.name } }] : []),\n { key: 'kubb.files_created', value: { intValue: filesCreated } },\n { key: 'kubb.status', value: { stringValue: status } },\n {\n key: 'kubb.plugins',\n value: { arrayValue: { values: pluginNames.map((name) => ({ stringValue: name })) } },\n },\n ...pluginNames.map((name) => ({ key: `kubb.plugin.${name}`, value: { boolValue: true } })),\n {\n key: 'kubb.plugin_options',\n value: {\n arrayValue: {\n values: plugins.map((p) => ({\n kvlistValue: {\n values: [\n { key: 'name', value: { stringValue: p.name } },\n {\n key: 'options',\n value: {\n stringValue: JSON.stringify({\n ...p.options,\n usedEnumNames: undefined,\n }),\n },\n },\n ],\n },\n })),\n },\n },\n },\n ]\n\n return {\n resourceSpans: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: 'kubb' } },\n { key: 'service.version', value: { stringValue: version } },\n { key: 'telemetry.sdk.language', value: { stringValue: 'nodejs' } },\n ],\n },\n scopeSpans: [\n {\n scope: { name: 'kubb-core', version },\n spans: [\n {\n traceId,\n spanId,\n name: command,\n kind: 1,\n startTimeUnixNano: String(startTimeNs),\n endTimeUnixNano: String(endTimeNs),\n attributes,\n status: {\n code: status === 'success' ? 1 : 2,\n },\n },\n ],\n },\n ],\n },\n ],\n }\n}\n\n/**\n * Send an OTLP payload to the Kubb endpoint. Respects `DO_NOT_TRACK` and\n * `KUBB_DISABLE_TELEMETRY`, and fails silently so telemetry never interrupts a run. No file\n * paths, OpenAPI specs, or secrets are sent.\n */\nexport async function sendTelemetry(payload: ReturnType<typeof buildOtlpPayload>): Promise<void> {\n if (isDisabled()) {\n return\n }\n\n try {\n await fetch(`${OTLP_ENDPOINT}/v1/traces`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Kubb-Telemetry-Version': '1',\n 'Kubb-Telemetry-Source': 'kubb-cli',\n },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(5_000),\n })\n } catch (_e) {\n // Fail silently, telemetry must never break the run\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"background-CK9olfbv.cjs","names":["path","getProjectKubbHome","readFile","connectSocket","delay","prepareConnection","createRequire","x"],"sources":["../src/runners/studio/background.ts"],"sourcesContent":["import { hash, randomBytes } from 'node:crypto'\nimport { readFile, rename, writeFile } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { connect as connectSocket, createServer } from 'node:net'\nimport path from 'node:path'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { x } from 'tinyexec'\nimport { getProjectKubbHome } from './credentials.ts'\nimport { prepareConnection, type PreparedConnection, type StudioOptions, type WorkerState } from './run.ts'\n\nexport type WorkerRecord = { id: string; pid: number; port: number; state: WorkerState }\nconst states: Array<WorkerState> = ['starting', 'connected', 'reconnecting', 'authentication required', 'stopped']\nexport const getWorkerLogPath = () => path.join(getProjectKubbHome(), 'worker.log')\nconst statePath = () => path.join(getProjectKubbHome(), 'worker.json')\n\nasync function readWorker(): Promise<WorkerRecord | null> {\n try {\n const value: unknown = JSON.parse(await readFile(statePath(), 'utf8'))\n if (!value || typeof value !== 'object') return null\n const record = value as Partial<WorkerRecord>\n return typeof record.id === 'string' &&\n /^[a-f0-9]{32}$/.test(record.id) &&\n Number.isInteger(record.pid) &&\n (record.pid ?? 0) > 1 &&\n Number.isInteger(record.port) &&\n (record.port ?? 0) > 0 &&\n (record.port ?? 0) <= 65535 &&\n states.includes(record.state as WorkerState)\n ? (record as WorkerRecord)\n : null\n } catch {\n return null\n }\n}\n\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\n/** Authenticated loopback IPC; a stored PID never authorizes sending a process signal. */\nfunction requestWorker(record: WorkerRecord, command: 'status' | 'stop'): Promise<WorkerRecord> {\n return new Promise((resolve, reject) => {\n const socket = connectSocket({ host: '127.0.0.1', port: record.port })\n let received = ''\n socket.setTimeout(2_000, () => socket.destroy(new Error('Worker control timed out')))\n socket.once('error', reject)\n socket.once('connect', () => socket.write(JSON.stringify({ id: record.id, command }) + '\\n'))\n socket.on('data', (chunk: Buffer) => {\n received += chunk.toString()\n if (received.length > 4096) socket.destroy(new Error('Invalid worker response'))\n })\n socket.once('end', () => {\n try {\n const response = JSON.parse(received) as WorkerRecord\n if (response.id !== record.id || response.pid !== record.pid || !states.includes(response.state)) throw new Error('Worker identity changed')\n resolve(response)\n } catch (error) {\n reject(error)\n }\n })\n })\n}\n\nexport async function getWorkerStatus(): Promise<{ running: boolean; state: WorkerState; pid?: number }> {\n const record = await readWorker()\n if (!record) return { running: false, state: 'stopped' }\n try {\n const response = await requestWorker(record, 'status')\n return { running: true, state: response.state, pid: response.pid }\n } catch {\n return { running: false, state: record.state === 'authentication required' ? record.state : 'stopped' }\n }\n}\n\nexport async function stopWorker(): Promise<void> {\n const record = await readWorker()\n if (!record) return\n try {\n await requestWorker(record, 'stop')\n } catch (error) {\n if (\n (error as NodeJS.ErrnoException).code !== 'ECONNREFUSED' &&\n isAlive(record.pid) &&\n record.state !== 'stopped' &&\n record.state !== 'authentication required'\n ) {\n throw new Error('Cannot verify the background worker. No process was signaled.')\n }\n return\n }\n const deadline = Date.now() + 15_000\n while (Date.now() < deadline) {\n try {\n await requestWorker(record, 'status')\n } catch {\n return\n }\n await delay(100)\n }\n throw new Error('The worker has not finished shutting down. Check worker.log and try again.')\n}\n\n/** The listening port is the project lock, so stale state never needs a PID-based takeover. */\nexport async function serveWorker(shutdown: AbortController) {\n const record: WorkerRecord = { id: randomBytes(16).toString('hex'), pid: process.pid, port: 0, state: 'starting' }\n const server = createServer((socket) => {\n let input = ''\n socket.setTimeout(2_000, () => socket.destroy())\n socket.on('error', () => {})\n socket.on('data', (chunk: Buffer) => {\n input += chunk.toString()\n if (input.length > 4096) {\n socket.destroy()\n return\n }\n if (!input.includes('\\n')) return\n try {\n const request = JSON.parse(input) as { id?: unknown; command?: unknown }\n if (request.id !== record.id || !['status', 'stop'].includes(String(request.command))) {\n socket.destroy()\n return\n }\n socket.end(JSON.stringify(record))\n if (request.command === 'stop') shutdown.abort()\n } catch {\n socket.destroy()\n }\n })\n })\n // Hash the project directory to choose its loopback port; fail on collisions.\n const port = 20_000 + (Number.parseInt(hash('sha256', getProjectKubbHome()).slice(0, 8), 16) % 30_000)\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, '127.0.0.1', resolve)\n })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw error\n // Let a simultaneous starter finish writing its record before checking ownership.\n await delay(100)\n const existing = await readWorker()\n if (existing?.port === port) {\n try {\n return { existing: await requestWorker(existing, 'status') }\n } catch {\n /* Refuse an unverified listener. */\n }\n }\n throw new Error('The project control port is occupied or another worker is starting. Try again after stopping that listener.')\n }\n record.port = port\n let saving = Promise.resolve()\n const save = () => {\n const value = JSON.stringify(record)\n saving = saving.then(async () => {\n const temporary = `${statePath()}.${record.id}`\n await writeFile(temporary, value, { mode: 0o600 })\n await rename(temporary, statePath())\n })\n return saving\n }\n try {\n await save()\n } catch (error) {\n server.close()\n throw error\n }\n return {\n async state(state: WorkerState) {\n record.state = state\n await save()\n },\n async close() {\n try {\n if (record.state !== 'authentication required') {\n record.state = 'stopped'\n await save()\n }\n } finally {\n await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())))\n }\n },\n }\n}\n\nexport type WorkerLaunch = { options: StudioOptions; prepared: PreparedConnection }\n\nexport async function startWorker(options: StudioOptions): Promise<void> {\n const current = await getWorkerStatus()\n if (current.running) {\n console.log(`Already running (${current.state}, PID ${current.pid}).`)\n return\n }\n const prepared = await prepareConnection(options)\n const entry = path.join(path.dirname(createRequire(import.meta.url).resolve('@kubb/cli/package.json')), 'dist', 'studioWorker.js')\n const child = x(process.execPath, [...process.execArgv, entry], {\n nodeOptions: { cwd: process.cwd(), detached: true, stdio: ['ignore', 'ignore', 'ignore', 'ipc'] },\n }).process\n if (!child) throw new Error('Background worker failed to start')\n try {\n const started = new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => done(new Error('Background worker startup timed out')), 20_000)\n const done = (error?: Error) => {\n clearTimeout(timer)\n child.removeListener('error', failed)\n child.removeListener('exit', exited)\n child.removeListener('message', ready)\n if (error) reject(error)\n else resolve()\n }\n const failed = (error: Error) => done(error)\n const exited = () => done(new Error(`Background worker exited during startup. Check ${getWorkerLogPath()}`))\n child.once('error', failed)\n child.once('exit', exited)\n const ready = (message: unknown) => {\n if (!message || typeof message !== 'object' || !('started' in message) || message.started !== true)\n return done(new Error('Invalid worker startup response'))\n done()\n }\n child.once('message', ready)\n child.send({ options: { ...options, autoOpen: false }, prepared } satisfies WorkerLaunch, (error) => {\n if (error) done(error)\n })\n })\n await started\n if (child.connected) child.disconnect()\n child.unref()\n const status = await getWorkerStatus()\n console.log(`Background worker ${status.state} (PID ${status.pid ?? child.pid}). Logs: ${getWorkerLogPath()}`)\n } catch (error) {\n // This is the child we just spawned, not a PID recovered from disk.\n child.kill('SIGTERM')\n throw error\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"background-DWasKvSw.js","names":["connectSocket","delay"],"sources":["../src/runners/studio/background.ts"],"sourcesContent":["import { hash, randomBytes } from 'node:crypto'\nimport { readFile, rename, writeFile } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { connect as connectSocket, createServer } from 'node:net'\nimport path from 'node:path'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { x } from 'tinyexec'\nimport { getProjectKubbHome } from './credentials.ts'\nimport { prepareConnection, type PreparedConnection, type StudioOptions, type WorkerState } from './run.ts'\n\nexport type WorkerRecord = { id: string; pid: number; port: number; state: WorkerState }\nconst states: Array<WorkerState> = ['starting', 'connected', 'reconnecting', 'authentication required', 'stopped']\nexport const getWorkerLogPath = () => path.join(getProjectKubbHome(), 'worker.log')\nconst statePath = () => path.join(getProjectKubbHome(), 'worker.json')\n\nasync function readWorker(): Promise<WorkerRecord | null> {\n try {\n const value: unknown = JSON.parse(await readFile(statePath(), 'utf8'))\n if (!value || typeof value !== 'object') return null\n const record = value as Partial<WorkerRecord>\n return typeof record.id === 'string' &&\n /^[a-f0-9]{32}$/.test(record.id) &&\n Number.isInteger(record.pid) &&\n (record.pid ?? 0) > 1 &&\n Number.isInteger(record.port) &&\n (record.port ?? 0) > 0 &&\n (record.port ?? 0) <= 65535 &&\n states.includes(record.state as WorkerState)\n ? (record as WorkerRecord)\n : null\n } catch {\n return null\n }\n}\n\nfunction isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\n/** Authenticated loopback IPC; a stored PID never authorizes sending a process signal. */\nfunction requestWorker(record: WorkerRecord, command: 'status' | 'stop'): Promise<WorkerRecord> {\n return new Promise((resolve, reject) => {\n const socket = connectSocket({ host: '127.0.0.1', port: record.port })\n let received = ''\n socket.setTimeout(2_000, () => socket.destroy(new Error('Worker control timed out')))\n socket.once('error', reject)\n socket.once('connect', () => socket.write(JSON.stringify({ id: record.id, command }) + '\\n'))\n socket.on('data', (chunk: Buffer) => {\n received += chunk.toString()\n if (received.length > 4096) socket.destroy(new Error('Invalid worker response'))\n })\n socket.once('end', () => {\n try {\n const response = JSON.parse(received) as WorkerRecord\n if (response.id !== record.id || response.pid !== record.pid || !states.includes(response.state)) throw new Error('Worker identity changed')\n resolve(response)\n } catch (error) {\n reject(error)\n }\n })\n })\n}\n\nexport async function getWorkerStatus(): Promise<{ running: boolean; state: WorkerState; pid?: number }> {\n const record = await readWorker()\n if (!record) return { running: false, state: 'stopped' }\n try {\n const response = await requestWorker(record, 'status')\n return { running: true, state: response.state, pid: response.pid }\n } catch {\n return { running: false, state: record.state === 'authentication required' ? record.state : 'stopped' }\n }\n}\n\nexport async function stopWorker(): Promise<void> {\n const record = await readWorker()\n if (!record) return\n try {\n await requestWorker(record, 'stop')\n } catch (error) {\n if (\n (error as NodeJS.ErrnoException).code !== 'ECONNREFUSED' &&\n isAlive(record.pid) &&\n record.state !== 'stopped' &&\n record.state !== 'authentication required'\n ) {\n throw new Error('Cannot verify the background worker. No process was signaled.')\n }\n return\n }\n const deadline = Date.now() + 15_000\n while (Date.now() < deadline) {\n try {\n await requestWorker(record, 'status')\n } catch {\n return\n }\n await delay(100)\n }\n throw new Error('The worker has not finished shutting down. Check worker.log and try again.')\n}\n\n/** The listening port is the project lock, so stale state never needs a PID-based takeover. */\nexport async function serveWorker(shutdown: AbortController) {\n const record: WorkerRecord = { id: randomBytes(16).toString('hex'), pid: process.pid, port: 0, state: 'starting' }\n const server = createServer((socket) => {\n let input = ''\n socket.setTimeout(2_000, () => socket.destroy())\n socket.on('error', () => {})\n socket.on('data', (chunk: Buffer) => {\n input += chunk.toString()\n if (input.length > 4096) {\n socket.destroy()\n return\n }\n if (!input.includes('\\n')) return\n try {\n const request = JSON.parse(input) as { id?: unknown; command?: unknown }\n if (request.id !== record.id || !['status', 'stop'].includes(String(request.command))) {\n socket.destroy()\n return\n }\n socket.end(JSON.stringify(record))\n if (request.command === 'stop') shutdown.abort()\n } catch {\n socket.destroy()\n }\n })\n })\n // Hash the project directory to choose its loopback port; fail on collisions.\n const port = 20_000 + (Number.parseInt(hash('sha256', getProjectKubbHome()).slice(0, 8), 16) % 30_000)\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, '127.0.0.1', resolve)\n })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw error\n // Let a simultaneous starter finish writing its record before checking ownership.\n await delay(100)\n const existing = await readWorker()\n if (existing?.port === port) {\n try {\n return { existing: await requestWorker(existing, 'status') }\n } catch {\n /* Refuse an unverified listener. */\n }\n }\n throw new Error('The project control port is occupied or another worker is starting. Try again after stopping that listener.')\n }\n record.port = port\n let saving = Promise.resolve()\n const save = () => {\n const value = JSON.stringify(record)\n saving = saving.then(async () => {\n const temporary = `${statePath()}.${record.id}`\n await writeFile(temporary, value, { mode: 0o600 })\n await rename(temporary, statePath())\n })\n return saving\n }\n try {\n await save()\n } catch (error) {\n server.close()\n throw error\n }\n return {\n async state(state: WorkerState) {\n record.state = state\n await save()\n },\n async close() {\n try {\n if (record.state !== 'authentication required') {\n record.state = 'stopped'\n await save()\n }\n } finally {\n await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())))\n }\n },\n }\n}\n\nexport type WorkerLaunch = { options: StudioOptions; prepared: PreparedConnection }\n\nexport async function startWorker(options: StudioOptions): Promise<void> {\n const current = await getWorkerStatus()\n if (current.running) {\n console.log(`Already running (${current.state}, PID ${current.pid}).`)\n return\n }\n const prepared = await prepareConnection(options)\n const entry = path.join(path.dirname(createRequire(import.meta.url).resolve('@kubb/cli/package.json')), 'dist', 'studioWorker.js')\n const child = x(process.execPath, [...process.execArgv, entry], {\n nodeOptions: { cwd: process.cwd(), detached: true, stdio: ['ignore', 'ignore', 'ignore', 'ipc'] },\n }).process\n if (!child) throw new Error('Background worker failed to start')\n try {\n const started = new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => done(new Error('Background worker startup timed out')), 20_000)\n const done = (error?: Error) => {\n clearTimeout(timer)\n child.removeListener('error', failed)\n child.removeListener('exit', exited)\n child.removeListener('message', ready)\n if (error) reject(error)\n else resolve()\n }\n const failed = (error: Error) => done(error)\n const exited = () => done(new Error(`Background worker exited during startup. Check ${getWorkerLogPath()}`))\n child.once('error', failed)\n child.once('exit', exited)\n const ready = (message: unknown) => {\n if (!message || typeof message !== 'object' || !('started' in message) || message.started !== true)\n return done(new Error('Invalid worker startup response'))\n done()\n }\n child.once('message', ready)\n child.send({ options: { ...options, autoOpen: false }, prepared } satisfies WorkerLaunch, (error) => {\n if (error) done(error)\n })\n })\n await started\n if (child.connected) child.disconnect()\n child.unref()\n const status = await getWorkerStatus()\n console.log(`Background worker ${status.state} (PID ${status.pid ?? child.pid}). Logs: ${getWorkerLogPath()}`)\n } catch (error) {\n // This is the child we just spawned, not a PID recovered from disk.\n child.kill('SIGTERM')\n throw error\n }\n}\n"],"mappings":"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"}
|