UNPKG

@kubb/cli

Version:

Command-line interface for Kubb, enabling easy generation of TypeScript, React-Query, Zod, and other code from OpenAPI specifications.

339 lines (335 loc) 10.9 kB
import { t as __name } from "./chunk-DKWOrOAv.js"; import { t as version } from "./package-veMf5zNr.js"; import { defineCommand } from "citty"; import { spawn } from "node:child_process"; import path from "node:path"; import process$1 from "node:process"; import { styleText } from "node:util"; import * as clack from "@clack/prompts"; import fs from "node:fs"; //#region src/utils/packageManager.ts const packageManagers = { pnpm: { name: "pnpm", lockFile: "pnpm-lock.yaml", installCommand: ["add", "-D"] }, yarn: { name: "yarn", lockFile: "yarn.lock", installCommand: ["add", "-D"] }, bun: { name: "bun", lockFile: "bun.lockb", installCommand: ["add", "-d"] }, npm: { name: "npm", lockFile: "package-lock.json", installCommand: ["install", "--save-dev"] } }; function detectPackageManager(cwd = process.cwd()) { const packageJsonPath = path.join(cwd, "package.json"); if (fs.existsSync(packageJsonPath)) try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); if (packageJson.packageManager) { const [name] = packageJson.packageManager.split("@"); if (name in packageManagers) return packageManagers[name]; } } catch {} for (const pm of Object.values(packageManagers)) if (fs.existsSync(path.join(cwd, pm.lockFile))) return pm; return packageManagers.npm; } function hasPackageJson(cwd = process.cwd()) { return fs.existsSync(path.join(cwd, "package.json")); } async function initPackageJson(cwd, packageManager) { spawn(packageManager.name, { npm: ["init", "-y"], pnpm: ["init"], yarn: ["init", "-y"], bun: ["init", "-y"] }[packageManager.name], { stdio: "inherit", cwd }); } async function installPackages(packages, packageManager, cwd = process.cwd()) { spawn(packageManager.name, [...packageManager.installCommand, ...packages], { stdio: "inherit", cwd }); } //#endregion //#region src/commands/init.ts const plugins = [ { value: "plugin-oas", label: "OpenAPI Parser", hint: "Required", packageName: "@kubb/plugin-oas", importName: "pluginOas", category: "core" }, { value: "plugin-ts", label: "TypeScript", hint: "Recommended", packageName: "@kubb/plugin-ts", importName: "pluginTs", category: "typescript" }, { value: "plugin-client", label: "Client (Fetch/Axios)", packageName: "@kubb/plugin-client", importName: "pluginClient", category: "typescript" }, { value: "plugin-react-query", label: "React Query / TanStack Query", packageName: "@kubb/plugin-react-query", importName: "pluginReactQuery", category: "query" }, { value: "plugin-solid-query", label: "Solid Query", packageName: "@kubb/plugin-solid-query", importName: "pluginSolidQuery", category: "query" }, { value: "plugin-svelte-query", label: "Svelte Query", packageName: "@kubb/plugin-svelte-query", importName: "pluginSvelteQuery", category: "query" }, { value: "plugin-vue-query", label: "Vue Query", packageName: "@kubb/plugin-vue-query", importName: "pluginVueQuery", category: "query" }, { value: "plugin-swr", label: "SWR", packageName: "@kubb/plugin-swr", importName: "pluginSwr", category: "query" }, { value: "plugin-zod", label: "Zod Schemas", packageName: "@kubb/plugin-zod", importName: "pluginZod", category: "validation" }, { value: "plugin-faker", label: "Faker.js Mocks", packageName: "@kubb/plugin-faker", importName: "pluginFaker", category: "mocking" }, { value: "plugin-msw", label: "MSW Handlers", packageName: "@kubb/plugin-msw", importName: "pluginMsw", category: "mocking" }, { value: "plugin-cypress", label: "Cypress Tests", packageName: "@kubb/plugin-cypress", importName: "pluginCypress", category: "testing" }, { value: "plugin-redoc", label: "ReDoc Documentation", packageName: "@kubb/plugin-redoc", importName: "pluginRedoc", category: "docs" } ]; function generateConfigFile(selectedPlugins, inputPath, outputPath) { return `import { defineConfig } from '@kubb/core' ${selectedPlugins.map((plugin) => `import { ${plugin.importName} } from '${plugin.packageName}'`).join("\n")} export default defineConfig({ root: '.', input: { path: '${inputPath}', }, output: { path: '${outputPath}', clean: true, }, plugins: [ ${selectedPlugins.map((plugin) => { if (plugin.value === "plugin-oas") return " pluginOas(),"; if (plugin.value === "plugin-ts") return ` pluginTs({\n output: {\n path: 'models',\n },\n }),`; if (plugin.value === "plugin-client") return ` pluginClient({\n output: {\n path: 'clients',\n },\n }),`; if (plugin.value === "plugin-react-query") return ` pluginReactQuery({\n output: {\n path: 'hooks',\n },\n }),`; if (plugin.value === "plugin-zod") return ` pluginZod({\n output: {\n path: 'zod',\n },\n }),`; if (plugin.value === "plugin-faker") return ` pluginFaker({\n output: {\n path: 'mocks',\n },\n }),`; if (plugin.value === "plugin-msw") return ` pluginMsw({\n output: {\n path: 'msw',\n },\n }),`; if (plugin.value === "plugin-swr") return ` pluginSwr({\n output: {\n path: 'hooks',\n },\n }),`; return ` ${plugin.importName}(),`; }).join("\n")} ], }) `; } const DEFAULT_INPUT_PATH = "./openapi.yaml"; const DEFAULT_OUTPUT_PATH = "./src/gen"; const DEFAULT_PLUGINS = ["plugin-oas", "plugin-ts"]; const command = defineCommand({ meta: { name: "init", description: "Initialize a new Kubb project with interactive setup" }, args: { yes: { type: "boolean", alias: "y", description: "Skip prompts and use default options", default: false } }, async run({ args }) { const cwd = process$1.cwd(); const yes = args.yes; clack.intro(styleText("bgCyan", styleText("black", " Kubb Init "))); try { let packageManager; if (!hasPackageJson(cwd)) { if (!yes) { const shouldInit = await clack.confirm({ message: "No package.json found. Would you like to create one?", initialValue: true }); if (clack.isCancel(shouldInit) || !shouldInit) { clack.cancel("Operation cancelled."); process$1.exit(0); } } packageManager = detectPackageManager(cwd); const spinner = clack.spinner(); spinner.start(`Initializing package.json with ${packageManager.name}`); await initPackageJson(cwd, packageManager); spinner.stop(`Created package.json with ${packageManager.name}`); } else { packageManager = detectPackageManager(cwd); clack.log.info(`Detected package manager: ${styleText("cyan", packageManager.name)}`); } let inputPath; if (yes) { inputPath = DEFAULT_INPUT_PATH; clack.log.info(`Using input path: ${styleText("cyan", inputPath)}`); } else { const inputPathResult = await clack.text({ message: "Where is your OpenAPI specification located?", placeholder: DEFAULT_INPUT_PATH, defaultValue: DEFAULT_INPUT_PATH, validate: (value) => { if (!value) return "Input path is required"; } }); if (clack.isCancel(inputPathResult)) { clack.cancel("Operation cancelled."); process$1.exit(0); } inputPath = inputPathResult; } let outputPath; if (yes) { outputPath = DEFAULT_OUTPUT_PATH; clack.log.info(`Using output path: ${styleText("cyan", outputPath)}`); } else { const outputPathResult = await clack.text({ message: "Where should the generated files be output?", placeholder: DEFAULT_OUTPUT_PATH, defaultValue: DEFAULT_OUTPUT_PATH, validate: (value) => { if (!value) return "Output path is required"; } }); if (clack.isCancel(outputPathResult)) { clack.cancel("Operation cancelled."); process$1.exit(0); } outputPath = outputPathResult; } let selectedPlugins; if (yes) { selectedPlugins = plugins.filter((plugin) => DEFAULT_PLUGINS.includes(plugin.value)); clack.log.info(`Using plugins: ${styleText("cyan", selectedPlugins.map((p) => p.label).join(", "))}`); } else { const selectedPluginValues = await clack.multiselect({ message: "Select plugins to use:", options: plugins.map((plugin) => ({ value: plugin.value, label: plugin.label, hint: plugin.hint })), initialValues: DEFAULT_PLUGINS, required: true }); if (clack.isCancel(selectedPluginValues)) { clack.cancel("Operation cancelled."); process$1.exit(0); } selectedPlugins = plugins.filter((plugin) => selectedPluginValues.includes(plugin.value)); } if (!selectedPlugins.find((p) => p.value === "plugin-oas")) selectedPlugins.unshift(plugins.find((p) => p.value === "plugin-oas")); const packagesToInstall = [ "@kubb/core", "@kubb/cli", "@kubb/agent", ...selectedPlugins.map((p) => p.packageName) ]; const spinner = clack.spinner(); spinner.start(`Installing ${packagesToInstall.length} packages with ${packageManager.name}`); try { await installPackages(packagesToInstall, packageManager, cwd); spinner.stop(`Installed ${packagesToInstall.length} packages`); } catch (error) { spinner.stop("Installation failed"); throw error; } const configSpinner = clack.spinner(); configSpinner.start("Creating kubb.config.ts"); const configContent = generateConfigFile(selectedPlugins, inputPath, outputPath); const configPath = path.join(cwd, "kubb.config.ts"); if (fs.existsSync(configPath)) { configSpinner.stop("kubb.config.ts already exists"); if (!yes) { const shouldOverwrite = await clack.confirm({ message: "kubb.config.ts already exists. Overwrite?", initialValue: false }); if (clack.isCancel(shouldOverwrite) || !shouldOverwrite) { clack.cancel("Keeping existing configuration. Packages have been installed."); process$1.exit(0); } } configSpinner.start("Overwriting kubb.config.ts"); } fs.writeFileSync(configPath, configContent, "utf-8"); configSpinner.stop("Created kubb.config.ts"); clack.outro(styleText("green", "✓ All set!") + "\n\n" + styleText("dim", "Next steps:") + "\n" + styleText("cyan", ` 1. Make sure your OpenAPI spec is at: ${inputPath}`) + "\n" + styleText("cyan", " 2. Generate code with: npx kubb generate") + "\n" + styleText("cyan", " Or start a stream server with: npx kubb start") + "\n" + styleText("cyan", ` 3. Find generated files in: ${outputPath}`) + "\n\n" + styleText("dim", `Using ${packageManager.name} • Kubb v${version}`)); } catch (error) { clack.log.error(styleText("red", "An error occurred during initialization")); if (error instanceof Error) clack.log.error(error.message); process$1.exit(1); } } }); //#endregion export { command as default }; //# sourceMappingURL=init-B5qnw1XS.js.map