@kubb/cli 5.11.4 → 5.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/{Telemetry-CwaVQsvD.js → Telemetry-BlAR8vxu.js} +3 -31
  2. package/dist/Telemetry-BlAR8vxu.js.map +1 -0
  3. package/dist/{Telemetry-Be1nltUh.cjs → Telemetry-CF1YNdR-.cjs} +2 -42
  4. package/dist/Telemetry-CF1YNdR-.cjs.map +1 -0
  5. package/dist/{background-mKdZ5HRx.cjs → background-CaZZul1n.cjs} +5 -4
  6. package/dist/background-CaZZul1n.cjs.map +1 -0
  7. package/dist/{background-CQPyCSn0.js → background-DKyMPeHm.js} +7 -5
  8. package/dist/background-DKyMPeHm.js.map +1 -0
  9. package/dist/{background-aTbwrgui.js → background-DUHqYiuQ.js} +1 -1
  10. package/dist/config-Bh00Hwpw.js +132 -0
  11. package/dist/config-Bh00Hwpw.js.map +1 -0
  12. package/dist/config-D63Gum4F.cjs +169 -0
  13. package/dist/config-D63Gum4F.cjs.map +1 -0
  14. package/dist/config-iVdOcpro.js +1 -0
  15. package/dist/credentials-CZK6zPLv.js.map +1 -1
  16. package/dist/credentials-DDaKqtJn.cjs.map +1 -1
  17. package/dist/errors-Cl69p_vI.cjs +43 -0
  18. package/dist/errors-Cl69p_vI.cjs.map +1 -0
  19. package/dist/errors-DmC83ALX.js +32 -0
  20. package/dist/errors-DmC83ALX.js.map +1 -0
  21. package/dist/index.cjs +872 -307
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.js +863 -298
  24. package/dist/index.js.map +1 -1
  25. package/dist/registry-CfYNeyOc.cjs +272 -0
  26. package/dist/registry-CfYNeyOc.cjs.map +1 -0
  27. package/dist/registry-D688-8EH.js +200 -0
  28. package/dist/registry-D688-8EH.js.map +1 -0
  29. package/dist/reporters-BHyW4d5Q.js +817 -0
  30. package/dist/reporters-BHyW4d5Q.js.map +1 -0
  31. package/dist/reporters-DrQJfnU2.cjs +829 -0
  32. package/dist/reporters-DrQJfnU2.cjs.map +1 -0
  33. package/dist/run-B4SKmzAG.js +1 -0
  34. package/dist/{run-Bczs5vbI.cjs → run-BldagXO-.cjs} +4 -3
  35. package/dist/{run-Bczs5vbI.cjs.map → run-BldagXO-.cjs.map} +1 -1
  36. package/dist/{run-B6VxJ_VM.js → run-C1FHgzNR.js} +3 -2
  37. package/dist/{run-B6VxJ_VM.js.map → run-C1FHgzNR.js.map} +1 -1
  38. package/dist/run-CYHpe0V_.cjs +227 -0
  39. package/dist/run-CYHpe0V_.cjs.map +1 -0
  40. package/dist/run-CaJbZO-f.cjs +94 -0
  41. package/dist/run-CaJbZO-f.cjs.map +1 -0
  42. package/dist/run-Cj-HJxYW.js +93 -0
  43. package/dist/run-Cj-HJxYW.js.map +1 -0
  44. package/dist/{run-BWRmQxtE.cjs → run-DkBJkKor.cjs} +172 -76
  45. package/dist/run-DkBJkKor.cjs.map +1 -0
  46. package/dist/run-S3xKq4bj.js +224 -0
  47. package/dist/run-S3xKq4bj.js.map +1 -0
  48. package/dist/{run-YHth5Afz.js → run-vBNko5TH.js} +166 -70
  49. package/dist/run-vBNko5TH.js.map +1 -0
  50. package/dist/{snapshot-D_rXnnMH.cjs → snapshot-CsIJRKEL.cjs} +50 -63
  51. package/dist/snapshot-CsIJRKEL.cjs.map +1 -0
  52. package/dist/{snapshot-Cilp10vE.js → snapshot-Cvo94y5d.js} +48 -61
  53. package/dist/snapshot-Cvo94y5d.js.map +1 -0
  54. package/dist/status-C-NPN63r.cjs +78 -0
  55. package/dist/status-C-NPN63r.cjs.map +1 -0
  56. package/dist/status-DTsZxa5Y.js +77 -0
  57. package/dist/status-DTsZxa5Y.js.map +1 -0
  58. package/dist/studioWorker.js +2 -2
  59. package/dist/summary-C22oNZu_.cjs +46 -0
  60. package/dist/summary-C22oNZu_.cjs.map +1 -0
  61. package/dist/summary-F11HQput.js +41 -0
  62. package/dist/summary-F11HQput.js.map +1 -0
  63. package/dist/text-28DwO_5A.cjs +341 -0
  64. package/dist/text-28DwO_5A.cjs.map +1 -0
  65. package/dist/text-BThr2r0T.js +239 -0
  66. package/dist/text-BThr2r0T.js.map +1 -0
  67. package/dist/utils-BLXM2UkN.cjs +727 -0
  68. package/dist/utils-BLXM2UkN.cjs.map +1 -0
  69. package/dist/utils-Cv3YUnSi.js +612 -0
  70. package/dist/utils-Cv3YUnSi.js.map +1 -0
  71. package/package.json +7 -7
  72. package/dist/Telemetry-Be1nltUh.cjs.map +0 -1
  73. package/dist/Telemetry-CwaVQsvD.js.map +0 -1
  74. package/dist/background-CQPyCSn0.js.map +0 -1
  75. package/dist/background-mKdZ5HRx.cjs.map +0 -1
  76. package/dist/config-B56ZuVhi.js +0 -95
  77. package/dist/config-B56ZuVhi.js.map +0 -1
  78. package/dist/config-Cs4N79Fm.cjs +0 -113
  79. package/dist/config-Cs4N79Fm.cjs.map +0 -1
  80. package/dist/output-HgzJqkSF.cjs +0 -1076
  81. package/dist/output-HgzJqkSF.cjs.map +0 -1
  82. package/dist/output-PhkPtNP7.js +0 -937
  83. package/dist/output-PhkPtNP7.js.map +0 -1
  84. package/dist/registry-DYUhC7rH.js +0 -115
  85. package/dist/registry-DYUhC7rH.js.map +0 -1
  86. package/dist/registry-tk-v78fd.cjs +0 -157
  87. package/dist/registry-tk-v78fd.cjs.map +0 -1
  88. package/dist/run-AfjIBtiL.js +0 -39
  89. package/dist/run-AfjIBtiL.js.map +0 -1
  90. package/dist/run-BWRmQxtE.cjs.map +0 -1
  91. package/dist/run-B_Gr9TJ4.cjs +0 -345
  92. package/dist/run-B_Gr9TJ4.cjs.map +0 -1
  93. package/dist/run-CwrI0wRy.js +0 -1
  94. package/dist/run-DDoJGjWT.cjs +0 -40
  95. package/dist/run-DDoJGjWT.cjs.map +0 -1
  96. package/dist/run-SYKj8-Q5.js +0 -343
  97. package/dist/run-SYKj8-Q5.js.map +0 -1
  98. package/dist/run-YHth5Afz.js.map +0 -1
  99. package/dist/snapshot-Cilp10vE.js.map +0 -1
  100. package/dist/snapshot-D_rXnnMH.cjs.map +0 -1
@@ -0,0 +1,224 @@
1
+ import "./rolldown-runtime-CRm0XQPb.js";
2
+ import { a as getElapsedMs, i as formatMs, n as trackRun, r as version } from "./Telemetry-BlAR8vxu.js";
3
+ import { a as listenForKeys, c as startWatcher, i as fetchUrlBody, l as trackBuild, s as startUrlWatcher, t as diffBuilds, u as createSpinner } from "./utils-Cv3YUnSi.js";
4
+ import { a as logOutro, d as logStep, i as logIntro, l as logError, m as createPlainWriter, o as logSpacer, s as logTip, t as pluralize, u as logInfo } from "./text-BThr2r0T.js";
5
+ import { d as logBanner, o as isOutdated } from "./registry-D688-8EH.js";
6
+ import { t as getConfigs } from "./config-Bh00Hwpw.js";
7
+ import { n as setupReporters, t as selectReporters } from "./reporters-BHyW4d5Q.js";
8
+ import { styleText } from "node:util";
9
+ import process from "node:process";
10
+ import path from "node:path";
11
+ import { Diagnostics, Hookable, cliReporter, createKubb, fileReporter, getInputKind, htmlReporter, jsonReporter, logLevel, memoryStorage } from "@kubb/core";
12
+ //#region src/runners/generate/run.ts
13
+ /** NPM registry endpoint used to check for @kubb/cli updates. */
14
+ const KUBB_NPM_PACKAGE_URL = "https://registry.npmjs.org/@kubb/cli/latest";
15
+ /** Upper bound in milliseconds for the npm update check, so a slow registry never stalls a run. */
16
+ const UPDATE_CHECK_TIMEOUT_MS = 3e3;
17
+ async function generate(options) {
18
+ const { input, hooks, dryRun = false } = options;
19
+ const report = trackRun({
20
+ command: "generate",
21
+ hrStart: process.hrtime()
22
+ });
23
+ const config = {
24
+ ...options.config,
25
+ input: input ?? options.config.input,
26
+ storage: dryRun ? memoryStorage() : options.config.storage,
27
+ output: dryRun ? {
28
+ ...options.config.output,
29
+ format: false,
30
+ lint: false,
31
+ postGenerate: []
32
+ } : options.config.output
33
+ };
34
+ const kubb = createKubb(config, { hooks });
35
+ const result = await kubb.generate();
36
+ await report({
37
+ plugins: kubb.config.plugins.flatMap((p) => p && typeof p === "object" ? [{
38
+ name: p.meta?.name ?? p.name ?? "",
39
+ options: p.options ?? {}
40
+ }] : []),
41
+ filesCreated: result.files.length,
42
+ status: result.success ? "success" : "failed"
43
+ });
44
+ return result.success;
45
+ }
46
+ /** The latest published version, `undefined` when the registry does not answer in time. Never rejects. */
47
+ async function fetchLatestVersion() {
48
+ try {
49
+ return (await (await fetch(KUBB_NPM_PACKAGE_URL, { signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) })).json()).version;
50
+ } catch {
51
+ return;
52
+ }
53
+ }
54
+ async function checkForUpdate({ hooks, latest }) {
55
+ const latestVersion = await latest;
56
+ if (latestVersion && isOutdated({
57
+ installed: version,
58
+ latest: latestVersion
59
+ })) await Diagnostics.emit(hooks, Diagnostics.update({
60
+ currentVersion: version,
61
+ latestVersion
62
+ }));
63
+ }
64
+ /** Prints `Rebuilt in 1.2s · +1 schema -2 operations`, without the names part when the spec kept the same names. */
65
+ function logRebuild({ diff: { schemas, operations }, durationMs }) {
66
+ const specChange = [
67
+ schemas.added && styleText("green", `+${pluralize(schemas.added, "schema")}`),
68
+ schemas.removed && styleText("red", `-${pluralize(schemas.removed, "schema")}`),
69
+ operations.added && styleText("green", `+${pluralize(operations.added, "operation")}`),
70
+ operations.removed && styleText("red", `-${pluralize(operations.removed, "operation")}`)
71
+ ].filter(Boolean).join(" ");
72
+ logStep([`Rebuilt in ${formatMs(durationMs)}`, specChange].filter(Boolean).join(styleText("dim", " · ")));
73
+ }
74
+ /**
75
+ * Runs the full Kubb generation lifecycle for the given CLI options.
76
+ * Loads configs, sets up the reporters (CLI `--reporter` picks which of `config.reporters` to trigger),
77
+ * checks for a newer version, and calls `generate` for each config entry.
78
+ */
79
+ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters, dryRun }) {
80
+ const latest = fetchLatestVersion();
81
+ const logLevel$1 = logLevel[logLevelKey] ?? logLevel.info;
82
+ const hooks = new Hookable();
83
+ const requestedNames = cliReporters?.length ? cliReporters : ["cli"];
84
+ const json = requestedNames.includes("json");
85
+ const quiet = logLevel$1 <= logLevel.silent || json;
86
+ const errorHooks = json ? new Hookable() : hooks;
87
+ if (json) setupReporters(errorHooks, {
88
+ logLevel: logLevel$1,
89
+ reporters: [cliReporter],
90
+ writer: createPlainWriter(console.error)
91
+ });
92
+ const banner = quiet ? void 0 : logBanner(version);
93
+ const loading = getConfigs({
94
+ configPath,
95
+ input,
96
+ watch,
97
+ logLevel: logLevelKey
98
+ });
99
+ loading.catch(() => void 0);
100
+ await banner;
101
+ if (!quiet) logIntro({ title: "Configuration" });
102
+ const configSpinner = createSpinner();
103
+ let configs;
104
+ let resolvedConfigPath;
105
+ try {
106
+ if (!quiet) configSpinner.start("Loading config");
107
+ const loaded = await loading;
108
+ configs = loaded.configs;
109
+ resolvedConfigPath = loaded.configPath;
110
+ if (!quiet) configSpinner.stop(`Loaded ${styleText("dim", path.relative(process.cwd(), resolvedConfigPath))}`);
111
+ } catch (error) {
112
+ if (!quiet) configSpinner.error("Config failed loading");
113
+ if (!json) setupReporters(hooks, {
114
+ logLevel: logLevel$1,
115
+ reporters: [cliReporter]
116
+ });
117
+ await Diagnostics.emit(errorHooks, error);
118
+ if (!quiet) logOutro(styleText("red", "✗ Configuration failed"));
119
+ process.exit(1);
120
+ }
121
+ const available = configs[0]?.reporters?.length ? configs[0].reporters : [
122
+ cliReporter,
123
+ jsonReporter,
124
+ fileReporter,
125
+ htmlReporter
126
+ ];
127
+ const reporters = selectReporters(available, requestedNames);
128
+ setupReporters(hooks, {
129
+ logLevel: logLevel$1,
130
+ reporters,
131
+ dryRun
132
+ });
133
+ await hooks.callHook("kubb:lifecycle:start", { version });
134
+ await checkForUpdate({
135
+ hooks,
136
+ latest
137
+ });
138
+ if (!quiet) logOutro(`${pluralize(configs.length, "config")} ready`);
139
+ try {
140
+ let anyFailed = false;
141
+ const tracker = watch ? trackBuild(hooks) : void 0;
142
+ const rebuilds = [];
143
+ let armKeys;
144
+ let queue = Promise.resolve();
145
+ for (const config of configs) {
146
+ const effectiveInput = input ?? config.input;
147
+ const inputKind = typeof effectiveInput === "string" ? getInputKind(effectiveInput) : void 0;
148
+ const watchPath = inputKind === "file" || inputKind === "url" ? effectiveInput : void 0;
149
+ if (watchPath && tracker) {
150
+ const label = styleText("yellow", `Watching ${inputKind === "url" ? watchPath : path.relative(process.cwd(), watchPath)}`);
151
+ let previous;
152
+ const build = async () => {
153
+ const hrStart = process.hrtime();
154
+ const succeeded = await generate({
155
+ input,
156
+ config,
157
+ hooks,
158
+ dryRun
159
+ });
160
+ const snapshot = tracker.take();
161
+ if (previous) logRebuild({
162
+ diff: diffBuilds(previous, snapshot),
163
+ durationMs: getElapsedMs(hrStart)
164
+ });
165
+ previous = snapshot;
166
+ logStep(armKeys ? `${label} ${styleText("dim", "r rebuild · q quit")}` : label);
167
+ armKeys?.();
168
+ if (succeeded) {
169
+ logSpacer();
170
+ logTip();
171
+ }
172
+ };
173
+ let pending = false;
174
+ const rebuild = () => {
175
+ if (pending) return queue;
176
+ pending = true;
177
+ queue = queue.then(() => {
178
+ pending = false;
179
+ return build();
180
+ }).catch((error) => void Diagnostics.emit(errorHooks, error));
181
+ return queue;
182
+ };
183
+ rebuilds.push(rebuild);
184
+ if (!quiet) armKeys ??= listenForKeys({
185
+ rebuild: () => rebuilds.forEach((trigger) => void trigger()),
186
+ quit: (code) => void Promise.resolve(hooks.callHook("kubb:lifecycle:end")).finally(() => process.exit(code))
187
+ });
188
+ const initialBody = inputKind === "url" ? await fetchUrlBody(watchPath) : void 0;
189
+ await rebuild();
190
+ if (inputKind === "url") startUrlWatcher(watchPath, rebuild, {
191
+ log: {
192
+ info: logInfo,
193
+ error: logError
194
+ },
195
+ initialBody
196
+ });
197
+ else await startWatcher([watchPath], rebuild, {
198
+ info: logInfo,
199
+ error: logError
200
+ });
201
+ } else try {
202
+ if (!await generate({
203
+ input,
204
+ config,
205
+ hooks,
206
+ dryRun
207
+ })) anyFailed = true;
208
+ } catch (configError) {
209
+ await Diagnostics.emit(errorHooks, configError);
210
+ anyFailed = true;
211
+ }
212
+ }
213
+ await hooks.callHook("kubb:lifecycle:end");
214
+ if (!quiet && !watch && !anyFailed) logTip();
215
+ if (anyFailed) process.exit(1);
216
+ } catch (error) {
217
+ await Diagnostics.emit(errorHooks, error);
218
+ process.exit(1);
219
+ }
220
+ }
221
+ //#endregion
222
+ export { generate, run };
223
+
224
+ //# sourceMappingURL=run-S3xKq4bj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-S3xKq4bj.js","names":["logLevel","logLevelMap"],"sources":["../src/runners/generate/run.ts"],"sourcesContent":["import path from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { formatMs, formatters, getElapsedMs, linters } from '@internals/utils'\nimport {\n Hookable,\n type CLIOptions,\n cliReporter,\n type Config,\n createKubb,\n Diagnostics,\n fileReporter,\n getInputKind,\n htmlReporter,\n jsonReporter,\n type KubbHooks,\n logLevel as logLevelMap,\n memoryStorage,\n type ReporterName,\n} from '@kubb/core'\nimport { version } from '../../../package.json'\nimport { trackRun } from '../../Telemetry.ts'\nimport { pluralize } from '../../ui/text.ts'\nimport setupReporters, { selectReporters } from '../../loggers/reporters.ts'\nimport { createPlainWriter } from '../../loggers/plainWriter.ts'\nimport { logIntro, logOutro, logSpacer, logTip } from '../../ui/intro.ts'\nimport { logError, logInfo, logStep } from '../../ui/log.ts'\nimport { logBanner } from '../../ui/mascot.ts'\nimport { createSpinner } from '../../ui/spinner.ts'\nimport { getConfigs } from '../../config.ts'\nimport { isOutdated } from '../../utils/registry.ts'\nimport { type BuildDiff, type BuildSnapshot, diffBuilds, fetchUrlBody, listenForKeys, startUrlWatcher, startWatcher, trackBuild } from './utils.ts'\n\n/** NPM registry endpoint used to check for @kubb/cli updates. */\nconst KUBB_NPM_PACKAGE_URL = 'https://registry.npmjs.org/@kubb/cli/latest' as const\n\n/** Upper bound in milliseconds for the npm update check, so a slow registry never stalls a run. */\nconst UPDATE_CHECK_TIMEOUT_MS = 3_000\n\n/** The formatter names from `Config['output'].format`, without `'auto'` (detection, not a tool) and `false` (skip). */\ntype FormatterName = Exclude<NonNullable<Config['output']['format']>, 'auto' | false>\ntype LinterName = Exclude<NonNullable<Config['output']['lint']>, 'auto' | false>\n\n// Pinned here, not in `@internals/utils` (which must not import `@kubb/core`): a tool added to the union without a descriptor fails to compile.\nformatters satisfies Record<FormatterName, unknown>\nlinters satisfies Record<LinterName, unknown>\n\ntype GenerateProps = {\n input?: string\n config: Config\n hooks: Hookable<KubbHooks>\n /**\n * When `true`, generates in memory instead of writing to disk, skips formatting, linting, and\n * post-generate commands, and prints what would change on disk.\n */\n dryRun?: boolean\n}\n\nexport async function generate(options: GenerateProps): Promise<boolean> {\n const { input, hooks, dryRun = false } = options\n\n const report = trackRun({ command: 'generate', hrStart: process.hrtime() })\n\n const config: Config = {\n ...options.config,\n input: input ?? options.config.input,\n // Dry-run never touches disk, regardless of the config's own storage driver.\n storage: dryRun ? memoryStorage() : options.config.storage,\n // Also keeps core's `hasOutputPasses` false, so dry-run skips the output manifest write too.\n output: dryRun ? { ...options.config.output, format: false, lint: false, postGenerate: [] } : options.config.output,\n }\n\n // Core formats, lints and runs `output.postGenerate` after an error-free build, and reports\n // their failures as coded diagnostics that reach the summary, the json report and the exit code.\n const kubb = createKubb(config, { hooks })\n const result = await kubb.generate()\n\n await report({\n // A setup that threw leaves no driver, so read the configured plugins instead.\n plugins: kubb.config.plugins.flatMap((p) =>\n p && typeof p === 'object' ? [{ name: p.meta?.name ?? p.name ?? '', options: (p.options ?? {}) as Record<string, unknown> }] : [],\n ),\n filesCreated: result.files.length,\n status: result.success ? 'success' : 'failed',\n })\n\n return result.success\n}\n\ntype GenerateCommandOptions = {\n input?: string\n configPath?: string\n logLevel: string\n watch: boolean\n reporters?: Array<ReporterName>\n dryRun?: boolean\n}\n\n/** The latest published version, `undefined` when the registry does not answer in time. Never rejects. */\nasync function fetchLatestVersion(): Promise<string | undefined> {\n try {\n const res = await fetch(KUBB_NPM_PACKAGE_URL, { signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) })\n const data = (await res.json()) as { version: string }\n return data.version\n } catch {\n // Ignore network errors\n return undefined\n }\n}\n\nasync function checkForUpdate({ hooks, latest }: { hooks: Hookable<KubbHooks>; latest: Promise<string | undefined> }): Promise<void> {\n const latestVersion = await latest\n if (latestVersion && isOutdated({ installed: version, latest: latestVersion })) {\n await Diagnostics.emit(hooks, Diagnostics.update({ currentVersion: version, latestVersion }))\n }\n}\n\n/** Prints `Rebuilt in 1.2s · +1 schema -2 operations`, without the names part when the spec kept the same names. */\nfunction logRebuild({ diff: { schemas, operations }, durationMs }: { diff: BuildDiff; durationMs: number }): void {\n const specChange = [\n schemas.added && styleText('green', `+${pluralize(schemas.added, 'schema')}`),\n schemas.removed && styleText('red', `-${pluralize(schemas.removed, 'schema')}`),\n operations.added && styleText('green', `+${pluralize(operations.added, 'operation')}`),\n operations.removed && styleText('red', `-${pluralize(operations.removed, 'operation')}`),\n ]\n .filter(Boolean)\n .join(' ')\n logStep([`Rebuilt in ${formatMs(durationMs)}`, specChange].filter(Boolean).join(styleText('dim', ' · ')))\n}\n\n/**\n * Runs the full Kubb generation lifecycle for the given CLI options.\n * Loads configs, sets up the reporters (CLI `--reporter` picks which of `config.reporters` to trigger),\n * checks for a newer version, and calls `generate` for each config entry.\n */\nexport async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters, dryRun }: GenerateCommandOptions): Promise<void> {\n // Started first so the registry request overlaps the banner and the config load.\n const latest = fetchLatestVersion()\n const logLevel = logLevelMap[logLevelKey as keyof typeof logLevelMap] ?? logLevelMap.info\n const hooks = new Hookable<KubbHooks>()\n\n // CLI `--reporter` selects which reporters to trigger by name, defaulting to `cli`. `defineConfig`\n // registers the built-in reporters on the config; a config exported without it falls back to them below.\n const requestedNames: Array<ReporterName> = cliReporters?.length ? cliReporters : ['cli']\n\n // The `json` reporter owns stdout, so the command writes nothing of its own around it.\n const json = requestedNames.includes('json')\n const quiet = logLevel <= logLevelMap.silent || json\n\n // Errors raised outside a build have no report entry. In `json` mode they go to a stderr logger,\n // so they are never silent and stdout stays one JSON document.\n const errorHooks = json ? new Hookable<KubbHooks>() : hooks\n if (json) setupReporters(errorHooks, { logLevel, reporters: [cliReporter], writer: createPlainWriter(console.error) })\n\n // The config loads while the banner plays; the output still prints in order once the banner is done.\n const banner = quiet ? undefined : logBanner(version)\n const loading = getConfigs({\n configPath,\n input,\n watch,\n logLevel: logLevelKey as CLIOptions['logLevel'],\n })\n // Handled below; this keeps a rejection during the banner from counting as unhandled.\n loading.catch(() => undefined)\n await banner\n\n if (!quiet) {\n logIntro({ title: 'Configuration' })\n }\n\n // Load the config first so `config.reporters` can pick the reporters. A failure here has no\n // reporter installed yet, so fall back to the default `cli` reporter to surface it.\n const configSpinner = createSpinner()\n let configs: Array<Config>\n let resolvedConfigPath: string\n try {\n if (!quiet) configSpinner.start('Loading config')\n\n const loaded = await loading\n configs = loaded.configs\n resolvedConfigPath = loaded.configPath\n\n if (!quiet) configSpinner.stop(`Loaded ${styleText('dim', path.relative(process.cwd(), resolvedConfigPath))}`)\n } catch (error) {\n if (!quiet) configSpinner.error('Config failed loading')\n\n if (!json) setupReporters(hooks, { logLevel, reporters: [cliReporter] })\n // A diagnostic thrown while the config file runs, such as invalid extension options, sits in the cause chain.\n await Diagnostics.emit(errorHooks, error)\n\n if (!quiet) logOutro(styleText('red', '✗ Configuration failed'))\n process.exit(1)\n }\n\n // Without a reporter nothing is printed, not even an error, so a config written without\n // `defineConfig` gets the same built-ins it would have registered.\n const available = configs[0]?.reporters?.length ? configs[0].reporters : [cliReporter, jsonReporter, fileReporter, htmlReporter]\n const reporters = selectReporters(available, requestedNames)\n setupReporters(hooks, { logLevel, reporters, dryRun })\n\n await hooks.callHook('kubb:lifecycle:start', { version })\n\n // Inside the bootstrap group, so an update notice reads as part of the setup rather than as part\n // of the first config's generation.\n await checkForUpdate({ hooks, latest })\n\n if (!quiet) logOutro(`${pluralize(configs.length, 'config')} ready`)\n\n try {\n let anyFailed = false\n // One tracker, one key listener and one build queue serve every watched config, so several configs never mix diffs or answer `r` twice.\n const tracker = watch ? trackBuild(hooks) : undefined\n const rebuilds: Array<() => Promise<void>> = []\n let armKeys: (() => void) | undefined\n // Configs share the hooks emitter, so every build, from any watcher or the `r` key, waits for the one before it.\n let queue = Promise.resolve()\n for (const config of configs) {\n const effectiveInput = input ?? config.input\n const inputKind = typeof effectiveInput === 'string' ? getInputKind(effectiveInput) : undefined\n const watchPath = inputKind === 'file' || inputKind === 'url' ? (effectiveInput as string) : undefined\n if (watchPath && tracker) {\n const label = styleText('yellow', `Watching ${inputKind === 'url' ? watchPath : path.relative(process.cwd(), watchPath)}`)\n let previous: BuildSnapshot | undefined\n // Don't removeAll() between builds, that would also drop logger and lifecycle\n // listeners. Plugin listeners are already disposed by safeBuild's dispose()\n // in its finally block, so re-running generate() on the same hooks emitter is safe.\n const build = async () => {\n const hrStart = process.hrtime()\n const succeeded = await generate({ input, config, hooks, dryRun })\n const snapshot = tracker.take()\n if (previous) logRebuild({ diff: diffBuilds(previous, snapshot), durationMs: getElapsedMs(hrStart) })\n previous = snapshot\n\n logStep(armKeys ? `${label} ${styleText('dim', 'r rebuild · q quit')}` : label)\n armKeys?.()\n if (succeeded) {\n logSpacer()\n logTip()\n }\n }\n // A trigger that lands while this config's build is queued joins it; one that lands during the build queues one more. Never rejects.\n let pending = false\n const rebuild = (): Promise<void> => {\n if (pending) return queue\n pending = true\n queue = queue\n .then(() => {\n pending = false\n return build()\n })\n .catch((error) => void Diagnostics.emit(errorHooks, error))\n return queue\n }\n rebuilds.push(rebuild)\n if (!quiet) {\n armKeys ??= listenForKeys({\n rebuild: () => rebuilds.forEach((trigger) => void trigger()),\n quit: (code) => void Promise.resolve(hooks.callHook('kubb:lifecycle:end')).finally(() => process.exit(code)),\n })\n }\n\n // For a URL input, capture the document before the build: it becomes the watcher's\n // change-detection baseline, so an edit landing before the first poll still rebuilds.\n // When the server is down the baseline stays undefined and the watcher rebuilds on its\n // first successful poll, so recovery with an unchanged document still generates output.\n const initialBody = inputKind === 'url' ? await fetchUrlBody(watchPath) : undefined\n\n // The watchers ignore their startup state, so run the first build here; a failing one keeps watching so the user can fix the input.\n await rebuild()\n\n if (inputKind === 'url') {\n startUrlWatcher(watchPath, rebuild, { log: { info: logInfo, error: logError }, initialBody })\n } else {\n await startWatcher([watchPath], rebuild, { info: logInfo, error: logError })\n }\n } else {\n try {\n const succeeded = await generate({ input, config, hooks, dryRun })\n if (!succeeded) anyFailed = true\n } catch (configError) {\n await Diagnostics.emit(errorHooks, configError)\n anyFailed = true\n }\n }\n }\n\n await hooks.callHook('kubb:lifecycle:end')\n\n // Watch mode prints a tip after each successful build. Regular generate commands need to\n // print one explicitly after the lifecycle group has closed so it stays outside the group.\n if (!quiet && !watch && !anyFailed) {\n logTip()\n }\n\n if (anyFailed) {\n process.exit(1)\n }\n } catch (error) {\n await Diagnostics.emit(errorHooks, error)\n process.exit(1)\n }\n}\n"],"mappings":"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"}
@@ -1,17 +1,51 @@
1
1
  import "./rolldown-runtime-CRm0XQPb.js";
2
- import { l as toError, n as trackRun, r as version, s as isCIEnvironment } from "./Telemetry-CwaVQsvD.js";
3
- import { g as canUseTTY, o as logIntro, p as plainLogger, r as logBlock, s as logOutro, t as createSpinner, u as logTip, x as KUBB_CONFIG_FILENAME } from "./output-PhkPtNP7.js";
2
+ import { n as toError } from "./errors-DmC83ALX.js";
3
+ import { n as trackRun, r as version, s as isCIEnvironment } from "./Telemetry-BlAR8vxu.js";
4
+ import { u as createSpinner } from "./utils-Cv3YUnSi.js";
5
+ import { _ as isRichOutput, a as logOutro, c as logBlock, d as logStep, g as canUseTTY, h as plainWriter, i as logIntro, n as toPermissionFlag, s as logTip } from "./text-BThr2r0T.js";
6
+ import { r as KUBB_CONFIG_FILENAME, t as getConfigs } from "./config-Bh00Hwpw.js";
4
7
  import { a as readCredentials, n as getCredentialsPath, o as writeCredentials, r as getMachineId, t as clearCredentials } from "./credentials-CZK6zPLv.js";
5
- import { r as setupReporters, t as getConfigs } from "./config-B56ZuVhi.js";
8
+ import { n as setupReporters } from "./reporters-BHyW4d5Q.js";
6
9
  import { styleText } from "node:util";
7
10
  import { hostname } from "node:os";
8
11
  import process from "node:process";
9
12
  import { x } from "tinyexec";
10
13
  import path from "node:path";
11
- import { Diagnostics, Hookable, cliReporter, logLevel } from "@kubb/core";
12
14
  import * as prompts from "@clack/prompts";
15
+ import { Diagnostics, Hookable, cliReporter, logLevel } from "@kubb/core";
13
16
  import { InvalidAgentTokenError, PairingCanceledError, connectAgent, defaultStudioUrl, pairAgent } from "@kubb/studio";
14
17
  import { AgentPermission } from "@kubb/studio/protocol";
18
+ //#region ../../internals/utils/src/copyToClipboard.ts
19
+ const COMMANDS = {
20
+ darwin: ["pbcopy"],
21
+ win32: ["clip.exe"],
22
+ linux: [
23
+ "xclip",
24
+ "-selection",
25
+ "clipboard"
26
+ ]
27
+ };
28
+ /**
29
+ * Copies text to the system clipboard with the platform's own tool. Resolves `false` in CI, on a
30
+ * platform without a known tool, or when the tool is missing or fails.
31
+ */
32
+ async function copyToClipboard(text) {
33
+ const command = process.platform === "linux" && process.env.WAYLAND_DISPLAY ? ["wl-copy"] : COMMANDS[process.platform];
34
+ if (!command || isCIEnvironment()) return false;
35
+ const [name, ...args] = command;
36
+ const run = x(name, args, {
37
+ timeout: 2e3,
38
+ nodeOptions: { stdio: [
39
+ "pipe",
40
+ "ignore",
41
+ "ignore"
42
+ ] }
43
+ });
44
+ run.process?.stdin?.once("error", () => {});
45
+ run.process?.stdin?.end(text);
46
+ return run.then(({ exitCode }) => exitCode === 0, () => false);
47
+ }
48
+ //#endregion
15
49
  //#region ../../internals/utils/src/openInBrowser.ts
16
50
  const ignore = () => {};
17
51
  /**
@@ -41,7 +75,9 @@ function openInBrowser(target) {
41
75
  */
42
76
  async function login({ studioUrl, autoOpen }, { signal, previousCredentials } = {}) {
43
77
  const spinner = createSpinner();
78
+ let retryUntil = 0;
44
79
  let waiting = false;
80
+ let countdown;
45
81
  try {
46
82
  const { token, agent } = await pairAgent({
47
83
  studioUrl,
@@ -51,99 +87,123 @@ async function login({ studioUrl, autoOpen }, { signal, previousCredentials } =
51
87
  machineId: await getMachineId(),
52
88
  signal,
53
89
  onCode: (session) => {
54
- console.log(`\nOpen ${styleText("cyan", session.verification_uri)} and approve the code ${styleText("bold", session.user_code)}`);
90
+ logStep([
91
+ "Pair this machine",
92
+ `${styleText("dim", "Open")} ${styleText("cyan", session.verification_uri)}`,
93
+ `${styleText("dim", "Code")} ${styleText("bold", session.user_code)}`
94
+ ].join("\n"));
55
95
  if (autoOpen) openInBrowser(session.verification_uri_complete);
56
- spinner.start("Waiting for approval");
96
+ const deadline = Date.now() + (session.expires_in > 0 ? session.expires_in : 600) * 1e3;
97
+ let copied = false;
98
+ const waitingText = () => `Waiting for approval ${styleText("dim", `expires in ${formatCountdown(deadline - Date.now())}${copied ? " · code copied" : ""} · ctrl+c to cancel`)}`;
99
+ clearInterval(countdown);
100
+ spinner.start(waitingText());
57
101
  waiting = true;
102
+ if (isRichOutput()) countdown = setInterval(() => Date.now() > retryUntil && spinner.message(waitingText()), 1e3);
103
+ copyToClipboard(session.user_code).then((done) => {
104
+ copied = done;
105
+ });
58
106
  },
59
- onRetry: (error) => spinner.message(`Could not reach Kubb Studio, retrying: ${error.message}`)
107
+ onRetry: (error) => {
108
+ retryUntil = Date.now() + 5e3;
109
+ spinner.message(`Could not reach Kubb Studio, retrying: ${error.message}`);
110
+ }
60
111
  });
61
- spinner.stop(`Paired as ${agent.name}`);
112
+ clearInterval(countdown);
113
+ spinner.stop(`Paired as ${agent.name}${agent.organizationSlug ? ` · org ${agent.organizationSlug}` : ""}`);
62
114
  const keepsIdentity = previousCredentials?.studioUrl === studioUrl && previousCredentials.agentId === agent.id;
63
115
  const credentials = {
64
116
  studioUrl,
65
117
  token,
66
118
  agentId: agent.id,
67
119
  agentSlug: agent.slug,
120
+ ...agent.organizationSlug ? { organizationSlug: agent.organizationSlug } : {},
68
121
  ...keepsIdentity && previousCredentials?.projects ? { projects: previousCredentials.projects } : {}
69
122
  };
70
123
  await writeCredentials(credentials);
71
124
  console.log(`Credentials stored in ${getCredentialsPath()}`);
72
125
  return credentials;
73
126
  } catch (error) {
127
+ clearInterval(countdown);
74
128
  if (waiting) spinner.stop(error instanceof PairingCanceledError ? "Pairing canceled" : "Pairing failed");
75
129
  throw error;
76
130
  }
77
131
  }
132
+ /** Time left on a pairing code as `m:ss`, never below `0:00`. */
133
+ function formatCountdown(ms) {
134
+ const seconds = Math.max(0, Math.ceil(ms / 1e3));
135
+ return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
136
+ }
78
137
  /**
79
- * What each permission is asked as, in the order the questions appear.
138
+ * What each permission is asked as, in the order the list shows them.
80
139
  */
81
140
  const PERMISSIONS = [
82
141
  {
83
142
  key: AgentPermission.READ,
84
- label: "read generated files",
85
- question: () => "Let Kubb Studio read the files a generation produced?"
143
+ label: "read source",
144
+ title: () => "Read generated files",
145
+ hint: "show code in the browser"
86
146
  },
87
147
  {
88
148
  key: AgentPermission.READ_AST,
89
- label: "read parsed spec",
90
- question: () => "Let Kubb Studio read the parsed spec (the Kubb AST)?"
149
+ label: "read spec",
150
+ title: () => "Read the parsed spec",
151
+ hint: "browse schemas and operations"
91
152
  },
92
153
  {
93
154
  key: AgentPermission.WRITE,
94
- label: "write generated files",
95
- question: (project) => `Let Kubb Studio write generated files into ${project}?`
155
+ label: "write files",
156
+ title: () => "Write files to disk",
157
+ hint: "generate from the browser"
96
158
  },
97
159
  {
98
160
  key: AgentPermission.CONFIG_EDIT,
99
- label: "edit kubb.config.ts",
100
- question: (_project, configPath) => `Let Kubb Studio change plugin options in ${configPath}?`
161
+ label: "edit config",
162
+ title: (configPath) => `Change ${configPath}`,
163
+ hint: "edit plugin options"
101
164
  },
102
165
  {
103
166
  key: AgentPermission.EXEC,
104
- label: "run formatter, linter, postGenerate",
105
- question: () => "Let Kubb Studio run the formatter, the linter, and output.postGenerate?"
167
+ label: "run tools",
168
+ title: () => "Run the formatter, linter, and postGenerate",
169
+ hint: "after each generation"
106
170
  }
107
171
  ];
108
172
  /**
109
- * One row per permission, for the connect banner and `kubb studio status`. A list rather than a
110
- * joined line: four labels this long read as one run-on sentence side by side.
173
+ * Two permissions per row, each marked granted or not, with the flags that grant the missing ones
174
+ * after the last row. For `kubb studio status`.
111
175
  */
112
176
  function formatPermissionRows(granted) {
113
- return PERMISSIONS.map(({ key, label }) => `${granted[key] ? styleText("green", "✔") : styleText("red", "✘")} ${label}`);
177
+ const width = Math.max(...PERMISSIONS.map(({ label }) => label.length));
178
+ const cells = PERMISSIONS.map(({ key, label }) => `${granted[key] ? styleText("green", "✓") : styleText("red", "✖")} ${label.padEnd(width)}`);
179
+ const rows = Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) => cells.slice(index * 2, index * 2 + 2).join(" "));
180
+ const flags = PERMISSIONS.filter(({ key }) => !granted[key]).map(({ key }) => toPermissionFlag(key)).join(" ");
181
+ return flags ? [...rows.slice(0, -1), `${rows.at(-1)} ${styleText("dim", flags)}`] : rows;
114
182
  }
115
183
  /**
116
184
  * Resolves Studio permissions for the current project.
117
- * Flags win, saved answers are reused, and new answers are stored in `~/.kubb/credentials.json`.
185
+ * Flags win, saved answers are reused, and the rest are asked in one list whose answers are stored
186
+ * in `~/.kubb/credentials.json`.
118
187
  */
119
188
  async function resolvePermissions(options, credentials, configPath = KUBB_CONFIG_FILENAME) {
120
189
  const project = process.cwd();
121
190
  const remembered = credentials.projects?.[project];
122
- const granted = {
123
- allowRead: false,
124
- allowReadAst: false,
125
- allowWrite: false,
126
- allowConfigEdit: false,
127
- allowExec: false
128
- };
129
- const answers = {};
191
+ const granted = Object.fromEntries(PERMISSIONS.map(({ key }) => [key, options.permission[key] || remembered?.[key] === true]));
192
+ const pending = PERMISSIONS.filter(({ key }) => !options.permission[key] && typeof remembered?.[key] !== "boolean");
193
+ if (!pending.length || isCIEnvironment() || !canUseTTY()) return granted;
130
194
  prompts.updateSettings({ withGuide: true });
131
- for (const { key, question } of PERMISSIONS) {
132
- if (options.permission[key] || typeof remembered?.[key] === "boolean") {
133
- granted[key] = options.permission[key] || remembered?.[key] === true;
134
- continue;
135
- }
136
- if (isCIEnvironment() || !canUseTTY()) {
137
- granted[key] = false;
138
- continue;
139
- }
140
- granted[key] = await prompts.confirm({
141
- message: question(project, configPath),
142
- initialValue: false
143
- }) === true;
144
- answers[key] = granted[key];
145
- }
146
- if (Object.keys(answers).length) await writeCredentials({
195
+ const selected = await prompts.multiselect({
196
+ message: "What may Kubb Studio do in this project?",
197
+ options: pending.map(({ key, title, hint }) => ({
198
+ value: key,
199
+ label: title(configPath),
200
+ hint
201
+ })),
202
+ required: false
203
+ });
204
+ if (prompts.isCancel(selected)) return granted;
205
+ const answers = Object.fromEntries(pending.map(({ key }) => [key, selected.includes(key)]));
206
+ await writeCredentials({
147
207
  ...credentials,
148
208
  projects: {
149
209
  ...credentials.projects,
@@ -153,7 +213,10 @@ async function resolvePermissions(options, credentials, configPath = KUBB_CONFIG
153
213
  }
154
214
  }
155
215
  });
156
- return granted;
216
+ return {
217
+ ...granted,
218
+ ...answers
219
+ };
157
220
  }
158
221
  /**
159
222
  * Loads the project's Kubb config the same way `kubb generate` does.
@@ -175,14 +238,6 @@ async function loadConfigs(options) {
175
238
  config
176
239
  };
177
240
  }
178
- /**
179
- * Explains a rejected token to the operator. Builds the error and nothing else, so each caller
180
- * decides what happens to the credentials.
181
- */
182
- function explainRejectedToken(error, reason) {
183
- if (reason === "reauthExhausted") return /* @__PURE__ */ new Error(`${error.message} Studio rejected the newly approved token too. Run \`kubb studio login\` and try again.`);
184
- return /* @__PURE__ */ new Error(`${error.message} Run \`kubb studio login\` to pair again.`);
185
- }
186
241
  /** Validates the project and resolves pairing and permissions before starting its worker. */
187
242
  async function prepareConnection(options, signal) {
188
243
  const { configPath } = await loadConfigs(options);
@@ -212,9 +267,10 @@ async function connect(options, context = {}) {
212
267
  events.once("SIGINT", stop);
213
268
  events.once("SIGTERM", stop);
214
269
  }
270
+ let unwatch;
215
271
  try {
216
272
  if (!context.prepared) {
217
- const { getWorkerStatus } = await import("./background-aTbwrgui.js");
273
+ const { getWorkerStatus } = await import("./background-DUHqYiuQ.js");
218
274
  if ((await getWorkerStatus()).running) throw new Diagnostics.Error({
219
275
  code: Diagnostics.code.unknown,
220
276
  severity: "error",
@@ -230,14 +286,23 @@ async function connect(options, context = {}) {
230
286
  setupReporters(hooks, {
231
287
  logLevel: logLevel[options.logLevel ?? "info"],
232
288
  reporters: [cliReporter],
233
- ...context.onState ? { logger: plainLogger } : {}
289
+ ...context.onState ? { writer: plainWriter } : {}
234
290
  });
291
+ const listens = !context.onState && process.stdin.isTTY === true && canUseTTY();
292
+ let background = false;
235
293
  hooks.hook("studio:connected", async () => {
236
294
  pairedAgain = false;
237
295
  await context.onState?.("connected");
296
+ if (listens && !unwatch) unwatch = watchKeys({
297
+ onBackground: () => {
298
+ background = true;
299
+ stop();
300
+ },
301
+ onStop: stop
302
+ });
238
303
  if (hinted || context.onState || options.logLevel === "silent") return;
239
304
  hinted = true;
240
- logBlock(styleText("dim", "Press Ctrl+C to disconnect"));
305
+ logBlock([`${styleText("dim", "Project")} ${path.basename(process.cwd())} ${styleText("dim", PERMISSIONS.filter(({ key }) => permissions[key]).map(({ label }) => label).join(" · ") || "no permissions")}`, styleText("dim", listens ? "Press b to keep running in the background, Ctrl+C to disconnect" : "Press Ctrl+C to disconnect")]);
241
306
  logTip();
242
307
  });
243
308
  hooks.hook("studio:reconnecting", () => context.onState?.("reconnecting"));
@@ -258,8 +323,10 @@ async function connect(options, context = {}) {
258
323
  if (!(error instanceof InvalidAgentTokenError)) throw error;
259
324
  await context.onState?.("authentication required");
260
325
  await clearCredentials();
261
- if (pairedAgain) throw explainRejectedToken(error, "reauthExhausted");
262
- if (context.onState || isCIEnvironment() || !canUseTTY()) throw explainRejectedToken(error, "nonInteractive");
326
+ if (pairedAgain) throw new Error(`${error.message} Studio rejected the newly approved token too. Run \`kubb studio login\` and try again.`);
327
+ if (context.onState || isCIEnvironment() || !canUseTTY()) throw new Error(`${error.message} Run \`kubb studio login\` to pair again.`);
328
+ unwatch?.();
329
+ unwatch = void 0;
263
330
  console.log(styleText("yellow", `${error.message} Pairing again...`));
264
331
  credentials = await login(options, {
265
332
  signal,
@@ -268,15 +335,50 @@ async function connect(options, context = {}) {
268
335
  pairedAgain = true;
269
336
  permissions = await resolvePermissions(options, credentials, configPath);
270
337
  }
338
+ await hooks.callHook("kubb:lifecycle:end");
339
+ unwatch?.();
340
+ if (background) {
341
+ const { startWorker } = await import("./background-DUHqYiuQ.js");
342
+ await startWorker(options);
343
+ if (options.logLevel !== "silent") logOutro("Running in the background · kubb studio stop");
344
+ return;
345
+ }
271
346
  if (!context.onState && signal.aborted && options.logLevel !== "silent") logOutro("Disconnected");
272
347
  } catch (error) {
273
348
  if (!(error instanceof PairingCanceledError)) throw error;
274
349
  } finally {
350
+ unwatch?.();
275
351
  events.off("SIGINT", stop);
276
352
  events.off("SIGTERM", stop);
277
353
  }
278
354
  }
279
355
  /**
356
+ * Listens for single keys on a TTY stdin. Raw mode turns Ctrl+C into a key instead of SIGINT, so it
357
+ * is handled here too. Returns the function that restores the terminal, safe to call twice.
358
+ */
359
+ function watchKeys({ onBackground, onStop }) {
360
+ const stdin = process.stdin;
361
+ const onData = (data) => {
362
+ const key = data.toString();
363
+ if (key === "b" || key === "B") onBackground();
364
+ if (key === "") onStop();
365
+ };
366
+ const cancelAlias = prompts.settings.aliases.get("");
367
+ prompts.settings.aliases.delete("");
368
+ stdin.setRawMode(true);
369
+ stdin.on("data", onData);
370
+ stdin.resume();
371
+ let restored = false;
372
+ return () => {
373
+ if (restored) return;
374
+ restored = true;
375
+ if (cancelAlias) prompts.settings.aliases.set("", cancelAlias);
376
+ stdin.off("data", onData);
377
+ stdin.setRawMode(false);
378
+ stdin.pause();
379
+ };
380
+ }
381
+ /**
280
382
  * Runs a Studio command from its parsed flags: builds the {@link StudioOptions}, prints the intro,
281
383
  * and reports the run to telemetry. A failure is printed and sets the exit code instead of throwing.
282
384
  */
@@ -285,13 +387,7 @@ async function runStudio(values, action, { block = false, json = false } = {}) {
285
387
  version,
286
388
  configPath: values.config,
287
389
  studioUrl: values.url ?? defaultStudioUrl,
288
- permission: {
289
- allowRead: values.allowRead ?? false,
290
- allowReadAst: values.allowReadAst ?? false,
291
- allowWrite: values.allowWrite ?? false,
292
- allowConfigEdit: values.allowConfigEdit ?? false,
293
- allowExec: values.allowExec ?? false
294
- },
390
+ permission: Object.fromEntries(Object.values(AgentPermission).map((key) => [key, values[key] ?? false])),
295
391
  autoOpen: values.open ?? true,
296
392
  logLevel: values.logLevel
297
393
  };
@@ -313,6 +409,6 @@ async function runStudio(values, action, { block = false, json = false } = {}) {
313
409
  }
314
410
  }
315
411
  //#endregion
316
- export { prepareConnection as a, login as i, formatPermissionRows as n, resolvePermissions as o, loadConfigs as r, runStudio as s, connect as t };
412
+ export { login as a, runStudio as c, loadConfigs as i, watchKeys as l, formatCountdown as n, prepareConnection as o, formatPermissionRows as r, resolvePermissions as s, connect as t };
317
413
 
318
- //# sourceMappingURL=run-YHth5Afz.js.map
414
+ //# sourceMappingURL=run-vBNko5TH.js.map