@kubb/cli 5.0.6 → 5.2.0

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 (101) hide show
  1. package/README.md +66 -0
  2. package/dist/{Telemetry-Fiymfxez.js → Telemetry-BCTY-Nzq.js} +3 -49
  3. package/dist/Telemetry-BCTY-Nzq.js.map +1 -0
  4. package/dist/{Telemetry-D-Jpt27O.cjs → Telemetry-DKfJQxnC.cjs} +8 -60
  5. package/dist/Telemetry-DKfJQxnC.cjs.map +1 -0
  6. package/dist/constants-CdxpgX3_.cjs +115 -0
  7. package/dist/constants-CdxpgX3_.cjs.map +1 -0
  8. package/dist/constants-DHRspBTo.js +92 -0
  9. package/dist/constants-DHRspBTo.js.map +1 -0
  10. package/dist/env-DHaFBZTX.cjs +82 -0
  11. package/dist/env-DHaFBZTX.cjs.map +1 -0
  12. package/dist/env-DqPKEL59.js +59 -0
  13. package/dist/env-DqPKEL59.js.map +1 -0
  14. package/dist/{errors-CZtH2dKk.cjs → errors-C-wEou02.cjs} +2 -19
  15. package/dist/errors-C-wEou02.cjs.map +1 -0
  16. package/dist/errors-C8yNfV7w.js +20 -0
  17. package/dist/errors-C8yNfV7w.js.map +1 -0
  18. package/dist/{generate-DcEkxx4R.cjs → generate-Btnbf2_0.cjs} +9 -6
  19. package/dist/generate-Btnbf2_0.cjs.map +1 -0
  20. package/dist/{generate-FtWB7SiZ.js → generate-PT8_WDJZ.js} +9 -6
  21. package/dist/generate-PT8_WDJZ.js.map +1 -0
  22. package/dist/index.cjs +14 -9
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.ts +2 -3
  25. package/dist/index.js +15 -10
  26. package/dist/index.js.map +1 -1
  27. package/dist/{init-ClG_bvAo.cjs → init-BwuaIvJr.cjs} +3 -3
  28. package/dist/{init-ClG_bvAo.cjs.map → init-BwuaIvJr.cjs.map} +1 -1
  29. package/dist/{init-B5G3LIla.js → init-dkS7T-wO.js} +3 -3
  30. package/dist/{init-B5G3LIla.js.map → init-dkS7T-wO.js.map} +1 -1
  31. package/dist/{mcp-DOHymcpS.js → mcp-B-GxGgVr.js} +8 -10
  32. package/dist/mcp-B-GxGgVr.js.map +1 -0
  33. package/dist/{mcp-ESVy9meL.cjs → mcp-B3B-rE7W.cjs} +8 -10
  34. package/dist/mcp-B3B-rE7W.cjs.map +1 -0
  35. package/dist/output-B2V2MN_C.cjs +137 -0
  36. package/dist/output-B2V2MN_C.cjs.map +1 -0
  37. package/dist/output-LZvrgF3z.js +89 -0
  38. package/dist/output-LZvrgF3z.js.map +1 -0
  39. package/dist/package-CrOAfe1h.js +6 -0
  40. package/dist/package-CrOAfe1h.js.map +1 -0
  41. package/dist/{package-CPbMhmPi.cjs → package-Dzjrfs0O.cjs} +2 -2
  42. package/dist/package-Dzjrfs0O.cjs.map +1 -0
  43. package/dist/{run-DSgLksGj.js → run-BzvsJ0Ek.js} +80 -112
  44. package/dist/run-BzvsJ0Ek.js.map +1 -0
  45. package/dist/run-CHfPmjaA.cjs +283 -0
  46. package/dist/run-CHfPmjaA.cjs.map +1 -0
  47. package/dist/{run-FPqeAEF4.cjs → run-DRBqVXX0.cjs} +93 -125
  48. package/dist/run-DRBqVXX0.cjs.map +1 -0
  49. package/dist/{run-ChKPZlIB.js → run-DReizVV9.js} +15 -4
  50. package/dist/run-DReizVV9.js.map +1 -0
  51. package/dist/run-DUY0lV8l.js +499 -0
  52. package/dist/run-DUY0lV8l.js.map +1 -0
  53. package/dist/run-DXPXPj4B.js +281 -0
  54. package/dist/run-DXPXPj4B.js.map +1 -0
  55. package/dist/{run-DCfdTu52.js → run-DvRhuqUM.js} +11 -4
  56. package/dist/run-DvRhuqUM.js.map +1 -0
  57. package/dist/run-Wkl7oK6Q.cjs +502 -0
  58. package/dist/run-Wkl7oK6Q.cjs.map +1 -0
  59. package/dist/{run-BV2tMjuz.cjs → run-aIkuYiQ6.cjs} +15 -4
  60. package/dist/run-aIkuYiQ6.cjs.map +1 -0
  61. package/dist/{run-Cs5FunVC.cjs → run-y7oTgJI3.cjs} +11 -4
  62. package/dist/run-y7oTgJI3.cjs.map +1 -0
  63. package/dist/studio-DKv2YLJZ.js +77 -0
  64. package/dist/studio-DKv2YLJZ.js.map +1 -0
  65. package/dist/studio-PDMxqFN0.cjs +76 -0
  66. package/dist/studio-PDMxqFN0.cjs.map +1 -0
  67. package/dist/{run-DzVVS0FV.cjs → utils-B0SrWoNh.cjs} +303 -323
  68. package/dist/utils-B0SrWoNh.cjs.map +1 -0
  69. package/dist/{run-D_TXufIV.js → utils-CzoNURwq.js} +274 -364
  70. package/dist/utils-CzoNURwq.js.map +1 -0
  71. package/dist/{validate-1sZ8wo6V.cjs → validate-BcB0tFQ6.cjs} +8 -13
  72. package/dist/validate-BcB0tFQ6.cjs.map +1 -0
  73. package/dist/{validate-DXT2hYwJ.js → validate-QpkCbIFJ.js} +8 -13
  74. package/dist/validate-QpkCbIFJ.js.map +1 -0
  75. package/package.json +15 -9
  76. package/dist/Telemetry-D-Jpt27O.cjs.map +0 -1
  77. package/dist/Telemetry-Fiymfxez.js.map +0 -1
  78. package/dist/errors-CMvLO2NE.js +0 -31
  79. package/dist/errors-CMvLO2NE.js.map +0 -1
  80. package/dist/errors-CZtH2dKk.cjs.map +0 -1
  81. package/dist/generate-DcEkxx4R.cjs.map +0 -1
  82. package/dist/generate-FtWB7SiZ.js.map +0 -1
  83. package/dist/mcp-DOHymcpS.js.map +0 -1
  84. package/dist/mcp-ESVy9meL.cjs.map +0 -1
  85. package/dist/package-BEDssgn1.js +0 -6
  86. package/dist/package-BEDssgn1.js.map +0 -1
  87. package/dist/package-CPbMhmPi.cjs.map +0 -1
  88. package/dist/run-BV2tMjuz.cjs.map +0 -1
  89. package/dist/run-ChKPZlIB.js.map +0 -1
  90. package/dist/run-Cs5FunVC.cjs.map +0 -1
  91. package/dist/run-DCfdTu52.js.map +0 -1
  92. package/dist/run-DSgLksGj.js.map +0 -1
  93. package/dist/run-D_TXufIV.js.map +0 -1
  94. package/dist/run-DzVVS0FV.cjs.map +0 -1
  95. package/dist/run-FPqeAEF4.cjs.map +0 -1
  96. package/dist/tools-DH1a7ZJH.js +0 -152
  97. package/dist/tools-DH1a7ZJH.js.map +0 -1
  98. package/dist/tools-bwKAf_r-.cjs +0 -175
  99. package/dist/tools-bwKAf_r-.cjs.map +0 -1
  100. package/dist/validate-1sZ8wo6V.cjs.map +0 -1
  101. package/dist/validate-DXT2hYwJ.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-DRBqVXX0.cjs","names":["availablePlugins","join","existsSync","readFileSync","process","fs","path","x","process","styleText","clack","createSpinner","initDefaults","availablePlugins","KUBB_PACKAGE_NAME","KUBB_CONFIG_FILENAME","path","fs"],"sources":["../../../internals/shared/src/init.ts","../src/tools.ts","../src/runners/init/utils.ts","../src/runners/init/run.ts"],"sourcesContent":["import { availablePlugins, KUBB_PACKAGE_NAME } from './constants.ts'\nimport type { PluginOption } from './types.ts'\n\n/**\n * Resolves a comma-separated plugin flag (e.g. `--plugins plugin-ts,plugin-zod`) into the\n * matching known plugin options. Unrecognized names are dropped, and a missing flag yields\n * an empty list.\n */\nexport function resolvePlugins(pluginsFlag: string | undefined): Array<PluginOption> {\n if (!pluginsFlag) {\n return []\n }\n const requested = pluginsFlag\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n return availablePlugins.filter((plugin) => requested.includes(plugin.value))\n}\n\nexport function generateConfigFile({\n selectedPlugins,\n inputPath,\n outputPath,\n}: {\n selectedPlugins: Array<PluginOption>\n inputPath: string\n outputPath: string\n}): string {\n const imports = selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join('\\n')\n\n const pluginConfigs = selectedPlugins.map((plugin) => ` ${plugin.importName}(),`).join('\\n')\n\n return `import { defineConfig } from 'kubb/config'\n${imports}\n\nexport default defineConfig({\n input: '${inputPath}',\n output: {\n path: '${outputPath}',\n clean: true,\n },\n plugins: [\n${pluginConfigs}\n ],\n})\n`\n}\n\n/**\n * Turns package names into install specifiers for the wizard.\n *\n * `kubb` is pinned to the exact version of the running CLI, since both ship from the same release\n * and resolving `kubb@beta` separately can land on a different version than the CLI doing the\n * scaffolding. Plugins release from their own repo on their own cadence, so they follow the\n * release channel of the CLI through its dist-tag.\n */\nexport function resolveInstallVersions({ packages, version }: { packages: Array<string>; version: string }): Array<string> {\n const prerelease = version.match(/-([a-z]+)/)?.[1]\n const tag = prerelease ?? 'latest'\n\n return packages.map((name) => (name === KUBB_PACKAGE_NAME ? `${name}@${version}` : `${name}@${tag}`))\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { formatters, linters } from '@internals/utils'\nimport type { Config } from '@kubb/core'\n\n/**\n * The configurable formatter names, mirrored from `Config['output'].format`. Excludes `'auto'`\n * (detection, not a tool) and `false` (skip). The `formatters`/`linters` tables below are pinned to\n * these so adding a tool to the config union without a descriptor fails to compile.\n */\ntype FormatterName = Exclude<NonNullable<Config['output']['format']>, 'auto' | false>\ntype LinterName = Exclude<NonNullable<Config['output']['lint']>, 'auto' | false>\n\n// Pinned to core's union here rather than in `@internals/utils`, which must not import `@kubb/core`:\n// adding a tool to the config union without a descriptor stays a compile error.\nformatters satisfies Record<FormatterName, unknown>\nlinters satisfies Record<LinterName, unknown>\n\nexport { detectTool, formatters, linters } from '@internals/utils'\n\nexport type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun'\n\n/**\n * Metadata describing a package manager's lock file and install command.\n */\nexport interface PackageManagerInfo {\n /**\n * Identifier used in CLI commands, e.g. `pnpm`, `yarn`.\n */\n name: PackageManagerName\n /**\n * Lock file name that uniquely identifies this package manager in a project root.\n */\n lockFile: string\n /**\n * Subcommands passed to the package manager binary to install a dev dependency.\n */\n installCommand: ReadonlyArray<string>\n}\n\n/**\n * Metadata for each supported package manager, keyed by its short name.\n *\n * @example\n * ```ts\n * packageManagers.pnpm.installCommand // ['add', '-D']\n * packageManagers.npm.lockFile // 'package-lock.json'\n * ```\n */\nconst packageManagers: Record<PackageManagerName, PackageManagerInfo> = {\n pnpm: {\n name: 'pnpm',\n lockFile: 'pnpm-lock.yaml',\n installCommand: ['add', '-D'],\n },\n yarn: {\n name: 'yarn',\n lockFile: 'yarn.lock',\n installCommand: ['add', '-D'],\n },\n bun: {\n name: 'bun',\n lockFile: 'bun.lockb',\n installCommand: ['add', '-d'],\n },\n npm: {\n name: 'npm',\n lockFile: 'package-lock.json',\n installCommand: ['install', '--save-dev'],\n },\n}\n\n/**\n * Minimal shape of `package.json` fields read during detection.\n */\ntype PackageJson = {\n /**\n * The `packageManager` field from `package.json` (e.g. `\"pnpm@9.0.0\"`).\n */\n packageManager?: string\n}\n\n/**\n * Detects the active package manager for the given directory.\n * Resolution order: `packageManager` field in `package.json`, then presence of a lock file.\n * Falls back to `npm` when no signal is found.\n *\n * @example\n * ```ts\n * detectPackageManager('/my/project') // { name: 'pnpm', lockFile: 'pnpm-lock.yaml', ... }\n * detectPackageManager() // falls back to npm when no lock file is found\n * ```\n */\nexport function detectPackageManager(cwd: string = process.cwd()): PackageManagerInfo {\n const packageJsonPath = join(cwd, 'package.json')\n if (existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as PackageJson\n const pmField = packageJson.packageManager\n if (typeof pmField === 'string') {\n const name = pmField.split('@')[0]\n if (name && name in packageManagers) {\n return packageManagers[name as PackageManagerName]\n }\n }\n } catch {\n // Continue to lock file detection\n }\n }\n\n for (const pm of Object.values(packageManagers)) {\n if (existsSync(join(cwd, pm.lockFile))) {\n return pm\n }\n }\n\n return packageManagers.npm\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { x } from 'tinyexec'\nimport type { PackageManagerInfo, PackageManagerName } from '../../tools.ts'\n\n/**\n * Returns `true` when a `package.json` exists at `cwd`.\n */\nexport function hasPackageJson(cwd: string = process.cwd()): boolean {\n return fs.existsSync(path.join(cwd, 'package.json'))\n}\n\n/**\n * Initializes a new `package.json` at `cwd` using the detected package manager.\n */\nexport async function initPackageJson(cwd: string, packageManager: PackageManagerInfo): Promise<void> {\n const commands: Record<PackageManagerName, Array<string>> = {\n npm: ['init', '-y'],\n pnpm: ['init'],\n yarn: ['init', '-y'],\n bun: ['init', '-y'],\n }\n\n await x(packageManager.name, commands[packageManager.name], {\n nodeOptions: { cwd, stdio: 'inherit' },\n throwOnError: true,\n })\n}\n\n/**\n * Installs the given packages at `cwd` using the detected package manager.\n */\nexport async function installPackages(packages: Array<string>, packageManager: PackageManagerInfo, cwd: string = process.cwd()): Promise<void> {\n await x(packageManager.name, [...packageManager.installCommand, ...packages], {\n nodeOptions: { cwd, stdio: 'inherit' },\n throwOnError: true,\n })\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport * as clack from '@clack/prompts'\nimport type { DryRunExtension } from '@gunshi/plugin-dryrun'\nimport {\n availablePlugins,\n generateConfigFile,\n initDefaults,\n KUBB_CONFIG_FILENAME,\n KUBB_PACKAGE_NAME,\n type PluginOption,\n resolveInstallVersions,\n resolvePlugins,\n} from '@internals/shared'\nimport { createSpinner, logError, logInfo, logIntro, logOutro, logWarn } from '../../loggers/output.ts'\nimport { hasPackageJson, initPackageJson, installPackages } from './utils.ts'\nimport { detectPackageManager } from '../../tools.ts'\n\nfunction cancelAndExit(message = 'Operation canceled.'): never {\n clack.cancel(message)\n process.exit(0)\n}\n\ntype InitOptions = {\n /**\n * When `true`, skips all interactive prompts and uses default values.\n */\n yes: boolean\n /**\n * Current `@kubb/cli` version string, shown in the closing outro.\n */\n version: string\n /**\n * Input path flag value from `--input`. When provided, skips the input prompt.\n */\n input?: string\n /**\n * Output directory flag value from `--output`. When provided, skips the output prompt.\n */\n output?: string\n /**\n * Comma-separated plugin list from `--plugins`, e.g. `'plugin-ts,plugin-zod'`. When provided, skips the plugin selection prompt.\n */\n plugins?: string\n /**\n * Dry-run extension from `@gunshi/plugin-dryrun`. When enabled, package installation and the\n * config file write are skipped.\n */\n dryRun: DryRunExtension\n}\n\n/**\n * Runs the interactive Kubb scaffolding wizard.\n * Detects the package manager, prompts for input/output paths and plugins, installs packages, and writes `kubb.config.ts`.\n * Pass `yes: true` to skip all prompts and use defaults.\n */\nexport async function run({ yes, version, input: inputFlag, output: outputFlag, plugins: pluginsFlag, dryRun }: InitOptions): Promise<void> {\n const cwd = process.cwd()\n\n logIntro({ title: styleText('bgCyan', styleText('black', ' Kubb Init ')) })\n\n /**\n * Returns `flag` when provided, the `defaultValue` when `yes` is set,\n * or calls `prompt()` for interactive input. Exits on cancellation.\n */\n async function resolveOrPrompt<T>(flag: T | undefined, defaultValue: T, logLabel: string, prompt: () => Promise<T | symbol>): Promise<T> {\n if (flag !== undefined) {\n logInfo(`${logLabel}: ${styleText('cyan', String(flag))}`)\n return flag\n }\n if (yes) {\n logInfo(`${logLabel}: ${styleText('cyan', String(defaultValue))}`)\n return defaultValue\n }\n const result = await prompt()\n if (clack.isCancel(result)) cancelAndExit()\n return result as T\n }\n\n try {\n // Check/create package.json, detect package manager once after the block\n if (!hasPackageJson(cwd)) {\n if (!yes) {\n const shouldInit = await clack.confirm({\n message: 'No package.json found. Would you like to create one?',\n initialValue: true,\n })\n\n if (clack.isCancel(shouldInit) || !shouldInit) {\n cancelAndExit()\n }\n }\n\n const packageManager = detectPackageManager(cwd)\n const spinner = createSpinner()\n spinner.start(`Initializing package.json with ${packageManager.name}`)\n await initPackageJson(cwd, packageManager)\n spinner.stop(`Created package.json with ${packageManager.name}`)\n }\n\n const packageManager = detectPackageManager(cwd)\n if (hasPackageJson(cwd)) {\n logInfo(`Detected package manager: ${styleText('cyan', packageManager.name)}`)\n }\n\n // Prompt for OpenAPI spec path\n const inputPath = await resolveOrPrompt(inputFlag, initDefaults.inputPath, 'Using input path', () =>\n clack.text({\n message: 'Where is your OpenAPI specification located?',\n placeholder: initDefaults.inputPath,\n defaultValue: initDefaults.inputPath,\n validate: (value) => {\n if (!value) return 'Input path is required'\n },\n }),\n )\n\n // Prompt for output directory\n const outputPath = await resolveOrPrompt(outputFlag, initDefaults.outputPath, 'Using output path', () =>\n clack.text({\n message: 'Where should the generated files be output?',\n placeholder: initDefaults.outputPath,\n defaultValue: initDefaults.outputPath,\n validate: (value) => {\n if (!value) return 'Output path is required'\n },\n }),\n )\n\n // Plugin selection\n const defaultPlugins = availablePlugins.filter((p) => (initDefaults.plugins as ReadonlyArray<string>).includes(p.value))\n const pluginLabel = (plugins: Array<PluginOption>) => styleText('cyan', plugins.map((p) => p.label).join(', '))\n\n const selectedPlugins: Array<PluginOption> = await (async () => {\n if (pluginsFlag) {\n const plugins = resolvePlugins(pluginsFlag)\n if (plugins.length === 0) {\n logWarn(`No valid plugins found in --plugins value; falling back to default: ${pluginLabel(defaultPlugins)}`)\n return defaultPlugins\n }\n logInfo(`Using plugins: ${pluginLabel(plugins)}`)\n return plugins\n }\n if (yes) {\n logInfo(`Using plugins: ${pluginLabel(defaultPlugins)}`)\n return defaultPlugins\n }\n const values = await clack.multiselect({\n message: 'Select plugins to use:',\n options: availablePlugins.map(({ value, label, hint }) => ({ value, label, hint })),\n initialValues: [...initDefaults.plugins],\n required: true,\n })\n if (clack.isCancel(values)) cancelAndExit()\n return availablePlugins.filter((p) => (values as Array<string>).includes(p.value))\n })()\n\n // Install packages, matching the release of the running CLI\n const packagesToInstall = resolveInstallVersions({ packages: [KUBB_PACKAGE_NAME, ...selectedPlugins.map((p) => p.packageName)], version })\n\n const spinner = createSpinner()\n spinner.start(`Installing ${packagesToInstall.length} packages with ${packageManager.name}`)\n\n try {\n await dryRun.run(() => installPackages(packagesToInstall, packageManager, cwd), {\n message: `install ${packagesToInstall.length} packages with ${packageManager.name}`,\n })\n spinner.stop(`Installed ${packagesToInstall.length} packages`)\n } catch (error) {\n spinner.stop('Installation failed')\n throw error\n }\n\n // Generate config file\n const configSpinner = createSpinner()\n configSpinner.start(`Creating ${KUBB_CONFIG_FILENAME}`)\n\n const configContent = generateConfigFile({ selectedPlugins, inputPath, outputPath })\n const configPath = path.join(cwd, KUBB_CONFIG_FILENAME)\n\n if (fs.existsSync(configPath)) {\n configSpinner.stop(`${KUBB_CONFIG_FILENAME} already exists`)\n\n if (!yes) {\n const shouldOverwrite = await clack.confirm({\n message: `${KUBB_CONFIG_FILENAME} already exists. Overwrite?`,\n initialValue: false,\n })\n\n if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) {\n cancelAndExit('Keeping existing configuration. Packages have been installed.')\n }\n }\n\n configSpinner.start(`Overwriting ${KUBB_CONFIG_FILENAME}`)\n }\n\n await dryRun.run(() => fs.promises.writeFile(configPath, configContent, 'utf-8'), { message: `write ${KUBB_CONFIG_FILENAME}` })\n\n configSpinner.stop(`Created ${KUBB_CONFIG_FILENAME}`)\n\n logOutro(\n styleText('green', '✓ All set!') +\n '\\n\\n' +\n styleText('dim', 'Next steps:') +\n '\\n' +\n styleText('cyan', ` 1. Make sure your OpenAPI spec is at: ${inputPath}`) +\n '\\n' +\n styleText('cyan', ' 2. Generate code with: npx kubb generate') +\n '\\n' +\n styleText('cyan', ` 3. Find generated files in: ${outputPath}`) +\n '\\n\\n' +\n styleText('dim', `Using ${packageManager.name} • Kubb v${version}`),\n )\n } catch (error) {\n logError(styleText('red', 'An error occurred during initialization'))\n if (error instanceof Error) {\n logError(error.message)\n }\n process.exit(1)\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { r as sendTelemetry, t as buildTelemetryEvent } from "./Telemetry-Fiymfxez.js";
3
- import { n as toError } from "./errors-CMvLO2NE.js";
2
+ import { t as toError } from "./errors-C8yNfV7w.js";
3
+ import { r as sendTelemetry, t as buildTelemetryEvent } from "./Telemetry-BCTY-Nzq.js";
4
+ import { t as version } from "./package-CrOAfe1h.js";
4
5
  import { styleText } from "node:util";
5
6
  import process from "node:process";
6
7
  //#region src/runners/validate/run.ts
@@ -39,7 +40,17 @@ async function run({ input, version }) {
39
40
  process.exit(1);
40
41
  }
41
42
  }
43
+ /**
44
+ * Loaded on demand by `index.ts`, so `@kubb/adapter-oas` stays out of the process for every other
45
+ * command.
46
+ */
47
+ const runner = async ({ values }) => {
48
+ await run({
49
+ input: values.input,
50
+ version
51
+ });
52
+ };
42
53
  //#endregion
43
- export { run };
54
+ export { runner };
44
55
 
45
- //# sourceMappingURL=run-ChKPZlIB.js.map
56
+ //# sourceMappingURL=run-DReizVV9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-DReizVV9.js","names":[],"sources":["../src/runners/validate/run.ts"],"sourcesContent":["import process from 'node:process'\nimport { styleText } from 'node:util'\nimport { toError } from '@internals/utils'\nimport type { CommandRunner } from 'gunshi'\nimport { buildTelemetryEvent, sendTelemetry } from '../../Telemetry.ts'\nimport { version } from '../../../package.json'\nimport type { definition } from '../../commands/validate.ts'\n\ntype ValidateOptions = {\n /**\n * Path or URL to the OpenAPI/Swagger file to validate.\n */\n input: string\n /**\n * Current `@kubb/cli` version string, used for the telemetry payload.\n */\n version: string\n}\n\n/**\n * Validates an OpenAPI/Swagger file at `input` using `@kubb/adapter-oas`.\n * Exits the process with code 1 on validation failure or missing dependency.\n */\nexport async function run({ input, version }: ValidateOptions): Promise<void> {\n const hrStart = process.hrtime()\n const report = (status: 'success' | 'failed') => sendTelemetry(buildTelemetryEvent({ command: 'validate', kubbVersion: version, hrStart, status }))\n\n try {\n const { adapterOas } = await import('@kubb/adapter-oas')\n\n const adapter = adapterOas()\n if (!adapter.validate) {\n throw new Error('The loaded adapter does not support validation.')\n }\n\n await adapter.validate(input, { throwOnError: true })\n await report('success')\n\n console.log('✅ Validation success')\n } catch (error) {\n await report('failed')\n if (error instanceof Error && /@kubb\\/adapter-oas/.test(error.message)) {\n console.error(styleText('red', 'The @kubb/adapter-oas package is not installed.'))\n console.error('')\n console.error('Install it with:')\n console.error(styleText('cyan', ' npm install @kubb/adapter-oas'))\n console.error(styleText('cyan', ' # or'))\n console.error(styleText('cyan', ' pnpm install @kubb/adapter-oas'))\n console.error('')\n }\n console.error('❌ Validation failed')\n console.error(toError(error).message)\n\n process.exit(1)\n }\n}\n\n/**\n * Loaded on demand by `index.ts`, so `@kubb/adapter-oas` stays out of the process for every other\n * command.\n */\nexport const runner: CommandRunner<{ args: typeof definition.args; extensions: {} }> = async ({ values }) => {\n await run({ input: values.input, version })\n}\n"],"mappings":";;;;;;;;;;;AAuBA,eAAsB,IAAI,EAAE,OAAO,WAA2C;CAC5E,MAAM,UAAU,QAAQ,OAAO;CAC/B,MAAM,UAAU,WAAiC,cAAc,oBAAoB;EAAE,SAAS;EAAY,aAAa;EAAS;EAAS;CAAO,CAAC,CAAC;CAElJ,IAAI;EACF,MAAM,EAAE,eAAe,MAAM,OAAO;EAEpC,MAAM,UAAU,WAAW;EAC3B,IAAI,CAAC,QAAQ,UACX,MAAM,IAAI,MAAM,iDAAiD;EAGnE,MAAM,QAAQ,SAAS,OAAO,EAAE,cAAc,KAAK,CAAC;EACpD,MAAM,OAAO,SAAS;EAEtB,QAAQ,IAAI,sBAAsB;CACpC,SAAS,OAAO;EACd,MAAM,OAAO,QAAQ;EACrB,IAAI,iBAAiB,SAAS,qBAAqB,KAAK,MAAM,OAAO,GAAG;GACtE,QAAQ,MAAM,UAAU,OAAO,iDAAiD,CAAC;GACjF,QAAQ,MAAM,EAAE;GAChB,QAAQ,MAAM,kBAAkB;GAChC,QAAQ,MAAM,UAAU,QAAQ,iCAAiC,CAAC;GAClE,QAAQ,MAAM,UAAU,QAAQ,QAAQ,CAAC;GACzC,QAAQ,MAAM,UAAU,QAAQ,kCAAkC,CAAC;GACnE,QAAQ,MAAM,EAAE;EAClB;EACA,QAAQ,MAAM,qBAAqB;EACnC,QAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC,OAAO;EAEpC,QAAQ,KAAK,CAAC;CAChB;AACF;;;;;AAMA,MAAa,SAA0E,OAAO,EAAE,aAAa;CAC3G,MAAM,IAAI;EAAE,OAAO,OAAO;EAAO;CAAQ,CAAC;AAC5C"}
@@ -0,0 +1,499 @@
1
+ import "./rolldown-runtime-C0LytTxp.js";
2
+ import { t as toError } from "./errors-C8yNfV7w.js";
3
+ import { c as runtime, r as sendTelemetry, t as buildTelemetryEvent } from "./Telemetry-BCTY-Nzq.js";
4
+ import { s as setupReporters, t as getConfigs } from "./utils-CzoNURwq.js";
5
+ import { n as isCIEnvironment, t as canUseTTY } from "./env-DqPKEL59.js";
6
+ import { t as version } from "./package-CrOAfe1h.js";
7
+ import { a as logIntro, n as logBlock, o as logOutro, t as createSpinner } from "./output-LZvrgF3z.js";
8
+ import { t as KUBB_CONFIG_FILENAME } from "./constants-DHRspBTo.js";
9
+ import { styleText } from "node:util";
10
+ import { homedir, hostname } from "node:os";
11
+ import process from "node:process";
12
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
13
+ import path from "node:path";
14
+ import { cliReporter, logLevel } from "@kubb/core";
15
+ import * as prompts from "@clack/prompts";
16
+ import { x } from "tinyexec";
17
+ import { PairingCanceledError, createFileStorage, defaultStudioUrl, pollForPairingToken, runConnection, setStorage, startPairing } from "@kubb/studio";
18
+ //#region ../../internals/utils/src/fs.ts
19
+ /**
20
+ * Reads the file at `path` as a UTF-8 string.
21
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const source = await read('./src/Pet.ts')
26
+ * ```
27
+ */
28
+ async function read(path) {
29
+ if (runtime.isBun) return Bun.file(path).text();
30
+ return readFile(path, { encoding: "utf8" });
31
+ }
32
+ /**
33
+ * Recursively removes `path`. Silently succeeds when `path` does not exist.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * await clean('./dist')
38
+ * ```
39
+ */
40
+ async function clean(path) {
41
+ return rm(path, {
42
+ recursive: true,
43
+ force: true
44
+ });
45
+ }
46
+ //#endregion
47
+ //#region src/runners/studio/credentials.ts
48
+ /**
49
+ * Root for everything the CLI persists between runs: the paired credential, the machine secret,
50
+ * the last Studio config, and the live-session registry. `KUBB_HOME` relocates all of it.
51
+ */
52
+ function getKubbHome() {
53
+ return process.env.KUBB_HOME ?? path.join(homedir(), ".kubb");
54
+ }
55
+ const CREDENTIALS_FILENAME = "credentials.json";
56
+ /**
57
+ * Absolute path of the stored credential, so callers can name it in output without rebuilding it.
58
+ */
59
+ function getCredentialsPath() {
60
+ return path.join(getKubbHome(), CREDENTIALS_FILENAME);
61
+ }
62
+ /**
63
+ * Reads the stored credential, or null when this machine has not paired yet or the file is
64
+ * unreadable or corrupt. A bad file is treated as "not paired" so `kubb studio login` can
65
+ * overwrite it rather than the CLI dying on it.
66
+ */
67
+ async function readCredentials() {
68
+ const raw = await read(getCredentialsPath()).catch(() => null);
69
+ if (!raw) return null;
70
+ try {
71
+ return JSON.parse(raw);
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ /**
77
+ * Writes the credential with owner-only permissions. The file holds a bearer token, so the mode
78
+ * is set at creation time rather than left to the process umask or fixed up afterward, which
79
+ * would leave the token briefly readable under the umask's default mode.
80
+ */
81
+ async function writeCredentials(credentials) {
82
+ const file = getCredentialsPath();
83
+ await mkdir(path.dirname(file), {
84
+ recursive: true,
85
+ mode: 448
86
+ });
87
+ await writeFile(file, JSON.stringify(credentials, null, 2), { mode: 384 });
88
+ }
89
+ /**
90
+ * Forgets the stored credential. Succeeds when there was nothing to forget.
91
+ */
92
+ async function clearCredentials() {
93
+ await clean(getCredentialsPath());
94
+ }
95
+ //#endregion
96
+ //#region src/runners/studio/run.ts
97
+ const ACTIONS = [
98
+ "connect",
99
+ "login",
100
+ "logout",
101
+ "status"
102
+ ];
103
+ /**
104
+ * Opens a URL in the user's browser. Best effort: a failure just means the user follows the
105
+ * printed link instead.
106
+ *
107
+ * `start` is a `cmd.exe` builtin, not an executable on PATH, so Windows runs it through `cmd`.
108
+ * The empty string is the window-title argument `start` expects before the URL.
109
+ */
110
+ async function openInBrowser(url) {
111
+ try {
112
+ if (process.platform === "win32") await x("cmd", [
113
+ "/c",
114
+ "start",
115
+ "",
116
+ url
117
+ ]);
118
+ else await x(process.platform === "darwin" ? "open" : "xdg-open", [url]);
119
+ } catch {}
120
+ }
121
+ /**
122
+ * Pairs this machine with Studio and stores the resulting token.
123
+ *
124
+ * The CLI holds a code and the browser approves it, rather than the user copying a token out of
125
+ * the UI. The token comes back over the CLI's own HTTPS POST, so it never lands in a URL, a
126
+ * server log, or a `Referer` header.
127
+ */
128
+ async function login({ studioUrl, autoOpen }, { signal, previousCredentials } = {}) {
129
+ const session = await startPairing({
130
+ studioUrl,
131
+ name: path.basename(process.cwd()),
132
+ hostname: hostname(),
133
+ signal
134
+ });
135
+ console.log(`\nOpen ${styleText("cyan", session.verification_uri)} and approve the code ${styleText("bold", session.user_code)}`);
136
+ if (autoOpen) await openInBrowser(session.verification_uri_complete);
137
+ const spinner = createSpinner();
138
+ spinner.start("Waiting for approval");
139
+ try {
140
+ const { token, agent } = await pollForPairingToken({
141
+ studioUrl,
142
+ session,
143
+ signal
144
+ });
145
+ spinner.stop(`Paired as ${agent.name}`);
146
+ const keepsIdentity = previousCredentials?.studioUrl === studioUrl && previousCredentials.agentId === agent.id;
147
+ const credentials = {
148
+ studioUrl,
149
+ token,
150
+ agentId: agent.id,
151
+ agentSlug: agent.slug,
152
+ ...keepsIdentity && previousCredentials?.projects ? { projects: previousCredentials.projects } : {}
153
+ };
154
+ await writeCredentials(credentials);
155
+ console.log(`Credentials stored in ${getCredentialsPath()}`);
156
+ return credentials;
157
+ } catch (error) {
158
+ spinner.stop(error instanceof PairingCanceledError ? "Pairing canceled" : "Pairing failed");
159
+ throw error;
160
+ }
161
+ }
162
+ /**
163
+ * What each permission is asked as, in the order the questions appear.
164
+ */
165
+ const PERMISSIONS = [
166
+ {
167
+ key: "allowWrite",
168
+ label: "write generated files",
169
+ question: (project) => `Let Kubb Studio write generated files into ${project}?`
170
+ },
171
+ {
172
+ key: "allowConfigEdit",
173
+ label: "edit kubb.config.ts",
174
+ question: (_project, configPath) => `Let Kubb Studio change plugin options in ${configPath}?`
175
+ },
176
+ {
177
+ key: "allowInput",
178
+ label: "use a Studio spec",
179
+ question: () => "Let Kubb Studio generate from an OpenAPI spec it sends, instead of the one on disk?"
180
+ },
181
+ {
182
+ key: "allowExec",
183
+ label: "run formatter, linter, postGenerate",
184
+ question: () => "Let Kubb Studio run the formatter, the linter, and output.postGenerate?"
185
+ }
186
+ ];
187
+ /**
188
+ * One row per permission, for the connect banner and `kubb studio status`. A list rather than a
189
+ * joined line: four labels this long read as one run-on sentence side by side.
190
+ */
191
+ function formatPermissionRows(granted) {
192
+ return PERMISSIONS.map(({ key, label }) => `${granted[key] ? styleText("green", "✔") : styleText("red", "✘")} ${label}`);
193
+ }
194
+ /**
195
+ * Resolves Studio permissions for the current project.
196
+ * Flags win, saved answers are reused, and new answers are stored unless `persist` is false.
197
+ */
198
+ async function resolvePermissions(options, credentials, configPath = KUBB_CONFIG_FILENAME, persist = true) {
199
+ const project = process.cwd();
200
+ const remembered = credentials.projects?.[project];
201
+ const granted = {
202
+ allowWrite: false,
203
+ allowConfigEdit: false,
204
+ allowInput: false,
205
+ allowExec: false
206
+ };
207
+ const answers = {};
208
+ for (const { key, question } of PERMISSIONS) {
209
+ if (options.permission[key] || typeof remembered?.[key] === "boolean") {
210
+ granted[key] = options.permission[key] || remembered?.[key] === true;
211
+ continue;
212
+ }
213
+ if (isCIEnvironment() || !canUseTTY()) {
214
+ granted[key] = false;
215
+ continue;
216
+ }
217
+ granted[key] = await prompts.confirm({
218
+ message: question(project, configPath),
219
+ initialValue: false
220
+ }) === true;
221
+ answers[key] = granted[key];
222
+ }
223
+ if (persist && Object.keys(answers).length) await writeCredentials({
224
+ ...credentials,
225
+ projects: {
226
+ ...credentials.projects,
227
+ [project]: {
228
+ ...remembered,
229
+ ...answers
230
+ }
231
+ }
232
+ });
233
+ return granted;
234
+ }
235
+ /**
236
+ * Loads the project's Kubb config the same way `kubb generate` does.
237
+ * Returns the first config.
238
+ */
239
+ async function loadConfigs(options) {
240
+ const { configPath, configs } = await getConfigs({
241
+ configPath: options.configPath,
242
+ logLevel: options.logLevel
243
+ });
244
+ const [config] = configs;
245
+ if (!config) throw new Error("Config not defined, create a kubb.config.ts or pass it with --config");
246
+ return {
247
+ configPath: path.relative(process.cwd(), configPath) || configPath,
248
+ config
249
+ };
250
+ }
251
+ /**
252
+ * Explains a rejected token to the operator. Builds the error and nothing else, so each caller
253
+ * decides what happens to the credentials.
254
+ */
255
+ function explainRejectedToken(error, reason) {
256
+ if (reason === "envToken") return /* @__PURE__ */ new Error(`${error.message} Pair again and update KUBB_AGENT_TOKEN.`);
257
+ if (reason === "reauthExhausted") return /* @__PURE__ */ new Error(`${error.message} Studio rejected the newly approved token too. Run \`kubb studio login\` and try again.`);
258
+ return /* @__PURE__ */ new Error(`${error.message} Run \`kubb studio login\` to pair again.`);
259
+ }
260
+ /**
261
+ * One `kubb studio` connect run: pairing if needed, connecting, and pairing again whenever Studio
262
+ * rejects the token. Reached through `connect()`, which is what the command runs.
263
+ */
264
+ var StudioConnection = class {
265
+ #options;
266
+ #configPath;
267
+ #shutdown = new AbortController();
268
+ #requestShutdown = () => this.#shutdown.abort();
269
+ #processEvents = process;
270
+ #credentials;
271
+ #hinted = false;
272
+ #hasReauthenticated = false;
273
+ #granted;
274
+ constructor(options, configPath) {
275
+ this.#options = options;
276
+ this.#configPath = configPath;
277
+ }
278
+ #reportDisconnected() {
279
+ if (this.#options.logLevel === "silent") return;
280
+ logOutro("Disconnected");
281
+ }
282
+ /**
283
+ * Connects and streams generation events until the process is stopped or Studio rejects the
284
+ * token.
285
+ *
286
+ * One `AbortController` covers the whole run: it cancels an in-flight pairing poll on Ctrl+C and
287
+ * ends the wait in `#connectAndWait`. Its signal listeners are armed once here, so a retried
288
+ * pairing cannot leave a duplicate behind.
289
+ */
290
+ async run() {
291
+ this.#processEvents.once("SIGINT", this.#requestShutdown);
292
+ this.#processEvents.once("SIGTERM", this.#requestShutdown);
293
+ try {
294
+ this.#credentials = await this.#resolveInitialCredentials();
295
+ this.#granted = await resolvePermissions(this.#options, this.#credentials, this.#configPath, !process.env.KUBB_AGENT_TOKEN);
296
+ this.#printBanner();
297
+ if (await runConnection({
298
+ credentials: this.#credentials,
299
+ signal: this.#shutdown.signal,
300
+ clientOptions: () => this.#clientOptions(),
301
+ onTokenRejected: ({ error, live }) => this.#handleRejection(error, live)
302
+ }) === "shutdown") this.#reportDisconnected();
303
+ } catch (error) {
304
+ if (error instanceof PairingCanceledError) return;
305
+ throw error;
306
+ } finally {
307
+ this.#processEvents.off("SIGINT", this.#requestShutdown);
308
+ this.#processEvents.off("SIGTERM", this.#requestShutdown);
309
+ }
310
+ }
311
+ async #resolveInitialCredentials() {
312
+ const envToken = process.env.KUBB_AGENT_TOKEN;
313
+ const stored = envToken ? null : await readCredentials();
314
+ const resolved = envToken ? {
315
+ studioUrl: this.#options.studioUrl,
316
+ token: envToken,
317
+ agentId: "",
318
+ agentSlug: ""
319
+ } : stored?.studioUrl === this.#options.studioUrl ? stored : null;
320
+ if (resolved) return resolved;
321
+ if (isCIEnvironment()) throw new Error(`Not paired with ${this.#options.studioUrl}. Set KUBB_AGENT_TOKEN, or run \`kubb studio login\` on a machine with a browser.`);
322
+ return login(this.#options, { signal: this.#shutdown.signal });
323
+ }
324
+ #printBanner() {
325
+ if (this.#options.logLevel === "silent") return;
326
+ const detail = (label, value) => `${styleText("dim", label.padEnd(7))} ${value}`;
327
+ logBlock([
328
+ detail("Studio", styleText("cyan", this.#options.studioUrl)),
329
+ detail("Project", path.basename(process.cwd())),
330
+ detail("Config", path.relative(process.cwd(), this.#configPath) || this.#configPath),
331
+ "",
332
+ styleText("dim", "Permissions"),
333
+ ...formatPermissionRows(this.#granted)
334
+ ]);
335
+ }
336
+ /**
337
+ * What every connection attempt opens with. Rebuilt per attempt, so a re-pair that changed the
338
+ * granted permissions takes effect on the next one.
339
+ */
340
+ #clientOptions() {
341
+ return {
342
+ studioUrl: this.#options.studioUrl,
343
+ configPath: this.#configPath,
344
+ version: this.#options.version,
345
+ loadConfig: async () => (await loadConfigs(this.#options)).config,
346
+ client: { kind: "cli" },
347
+ root: process.cwd(),
348
+ permissions: this.#granted,
349
+ installLogger: async (hooks) => {
350
+ await setupReporters(hooks, {
351
+ logLevel: logLevel[this.#options.logLevel ?? "info"],
352
+ reporters: [cliReporter]
353
+ });
354
+ hooks.hook("studio:connected", () => {
355
+ if (this.#hinted || this.#options.logLevel === "silent") return;
356
+ this.#hinted = true;
357
+ logBlock(styleText("dim", "Press Ctrl+C to disconnect"));
358
+ });
359
+ }
360
+ };
361
+ }
362
+ /**
363
+ * Throws when a rejected token cannot be replaced by pairing again: this run already paired once
364
+ * and was rejected anyway, or there is no browser to approve a new pairing.
365
+ */
366
+ #assertCanReauthenticate(error) {
367
+ if (this.#hasReauthenticated) throw explainRejectedToken(error, "reauthExhausted");
368
+ if (isCIEnvironment() || !canUseTTY()) throw explainRejectedToken(error, "nonInteractive");
369
+ }
370
+ /**
371
+ * Studio rejected the token, either before a session ever opened or once one was already live.
372
+ * Keeping a rejected token only produces 401s on every run, so it is forgotten and paired again.
373
+ * Returns the credential to reconnect with, or null when the run is over.
374
+ */
375
+ async #handleRejection(error, live) {
376
+ if (process.env.KUBB_AGENT_TOKEN) throw explainRejectedToken(error, "envToken");
377
+ if (!live) await clearCredentials();
378
+ this.#assertCanReauthenticate(error);
379
+ console.log(styleText("yellow", live ? `${error.message} Studio needs you to approve access again.` : `${error.message} Pairing again...`));
380
+ if (live) await clearCredentials();
381
+ const credentials = await this.#reauthenticate();
382
+ if (!credentials && live) this.#reportDisconnected();
383
+ return credentials;
384
+ }
385
+ /**
386
+ * Re-pairs and stores the resulting credentials. Returns null when the operator canceled it.
387
+ */
388
+ async #reauthenticate() {
389
+ try {
390
+ this.#credentials = await login(this.#options, {
391
+ signal: this.#shutdown.signal,
392
+ previousCredentials: this.#credentials
393
+ });
394
+ } catch (loginError) {
395
+ if (loginError instanceof PairingCanceledError) return null;
396
+ throw loginError;
397
+ }
398
+ this.#hasReauthenticated = true;
399
+ this.#granted = await resolvePermissions(this.#options, this.#credentials, this.#configPath, !process.env.KUBB_AGENT_TOKEN);
400
+ return this.#credentials;
401
+ }
402
+ };
403
+ /**
404
+ * Connects this project to Studio and streams generation events until the process is stopped or
405
+ * Studio rejects the token.
406
+ */
407
+ async function connect(options) {
408
+ const { configPath } = await loadConfigs(options);
409
+ await new StudioConnection(options, configPath).run();
410
+ }
411
+ /**
412
+ * Reports the paired agent and any saved permissions for the current project.
413
+ */
414
+ async function status(options) {
415
+ const credentials = await readCredentials();
416
+ if (!credentials) {
417
+ console.log("Not paired. Run `kubb studio login`.");
418
+ return;
419
+ }
420
+ console.log(`Paired with ${credentials.studioUrl} as ${styleText("cyan", credentials.agentSlug || credentials.agentId)}`);
421
+ if (credentials.studioUrl !== options.studioUrl) console.log(styleText("yellow", `Connecting to ${options.studioUrl} needs pairing again.`));
422
+ const remembered = credentials.projects?.[process.cwd()];
423
+ if (!remembered) {
424
+ console.log(styleText("dim", "No saved permissions for this project. Run `kubb studio` to connect and choose."));
425
+ return;
426
+ }
427
+ console.log(styleText("dim", "Saved permissions"));
428
+ for (const row of formatPermissionRows({
429
+ allowWrite: remembered.allowWrite === true,
430
+ allowConfigEdit: remembered.allowConfigEdit === true,
431
+ allowInput: remembered.allowInput === true,
432
+ allowExec: remembered.allowExec === true
433
+ })) console.log(row);
434
+ }
435
+ /**
436
+ * Runs a `kubb studio` action and reports the outcome to telemetry.
437
+ */
438
+ async function run(options) {
439
+ setStorage(createFileStorage(path.join(getKubbHome(), "cache")));
440
+ const hrStart = process.hrtime();
441
+ const report = (status) => sendTelemetry(buildTelemetryEvent({
442
+ command: "studio",
443
+ kubbVersion: options.version,
444
+ hrStart,
445
+ status
446
+ }));
447
+ try {
448
+ if (options.logLevel !== "silent") logIntro({
449
+ title: `Kubb Studio ${styleText("dim", `v${options.version}`)}`,
450
+ warning: styleText("yellow", "This feature is still under development, use with caution"),
451
+ block: options.action === "connect"
452
+ });
453
+ switch (options.action) {
454
+ case "login":
455
+ await login(options);
456
+ break;
457
+ case "logout":
458
+ await clearCredentials();
459
+ console.log("Signed out of Kubb Studio.");
460
+ break;
461
+ case "status":
462
+ await status(options);
463
+ break;
464
+ case "connect":
465
+ await connect(options);
466
+ break;
467
+ default: throw new Error(`Unknown action "${options.action}", expected one of ${ACTIONS.join(", ")}`);
468
+ }
469
+ await report("success");
470
+ } catch (error) {
471
+ await report("failed");
472
+ console.error(toError(error).message);
473
+ process.exitCode = 1;
474
+ }
475
+ }
476
+ /**
477
+ * Maps the parsed `kubb studio` flags onto {@link run}. Loaded on demand by `index.ts`, so the
478
+ * Studio client stays out of the process for every other command.
479
+ */
480
+ const runner = async ({ values }) => {
481
+ await run({
482
+ action: values.action ?? "connect",
483
+ version,
484
+ configPath: values.config,
485
+ studioUrl: values.url ?? defaultStudioUrl,
486
+ permission: {
487
+ allowWrite: values.allowWrite,
488
+ allowConfigEdit: values.allowConfigEdit,
489
+ allowInput: values.allowInput,
490
+ allowExec: values.allowExec
491
+ },
492
+ autoOpen: values.open,
493
+ logLevel: values.logLevel
494
+ });
495
+ };
496
+ //#endregion
497
+ export { runner };
498
+
499
+ //# sourceMappingURL=run-DUY0lV8l.js.map