From aecde5a54ec545fe4c7f67716298b128c610eb5f Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 12 Aug 2025 07:40:19 +0530 Subject: [PATCH] feat(cli): add programmatic api (#494) --- .changeset/eleven-walls-tease.md | 5 + apps/cli/package.json | 14 +- apps/cli/src/cli.ts | 3 + .../project-generation/command-handlers.ts | 295 ++++++++++----- apps/cli/src/index.ts | 132 ++++++- apps/cli/src/types.ts | 20 ++ apps/cli/src/utils/errors.ts | 13 + apps/cli/src/utils/project-directory.ts | 7 + apps/cli/src/validation.ts | 154 ++++++-- apps/cli/test/cli.smoke.test.ts | 88 +++-- apps/cli/test/programmatic-api.test.ts | 340 ++++++++++++++++++ apps/cli/tsdown.config.ts | 3 +- apps/web/content/docs/cli/index.mdx | 36 ++ apps/web/content/docs/cli/meta.json | 2 +- apps/web/content/docs/cli/options.mdx | 42 +++ .../web/content/docs/cli/programmatic-api.mdx | 276 ++++++++++++++ bun.lock | 66 ++-- package.json | 2 +- 18 files changed, 1295 insertions(+), 203 deletions(-) create mode 100644 .changeset/eleven-walls-tease.md create mode 100644 apps/cli/src/cli.ts create mode 100644 apps/cli/test/programmatic-api.test.ts create mode 100644 apps/web/content/docs/cli/programmatic-api.mdx diff --git a/.changeset/eleven-walls-tease.md b/.changeset/eleven-walls-tease.md new file mode 100644 index 0000000..c9106e0 --- /dev/null +++ b/.changeset/eleven-walls-tease.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Add programmatic API diff --git a/apps/cli/package.json b/apps/cli/package.json index 34264ae..fe83685 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -6,7 +6,7 @@ "license": "MIT", "author": "Aman Varshney", "bin": { - "create-better-t-stack": "dist/index.js" + "create-better-t-stack": "dist/cli.js" }, "files": [ "templates", @@ -53,6 +53,12 @@ "test:with-build": "bun run build && WITH_BUILD=1 vitest run", "prepublishOnly": "npm run build" }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, "dependencies": { "@clack/prompts": "^0.11.0", "consola": "^3.4.2", @@ -65,12 +71,12 @@ "picocolors": "^1.1.1", "trpc-cli": "^0.10.2", "ts-morph": "^26.0.0", - "zod": "^4.0.15" + "zod": "^4.0.17" }, "devDependencies": { "@types/fs-extra": "^11.0.4", - "@types/node": "^24.2.0", - "tsdown": "^0.13.3", + "@types/node": "^24.2.1", + "tsdown": "^0.14.1", "typescript": "^5.9.2", "vitest": "^3.2.4" } diff --git a/apps/cli/src/cli.ts b/apps/cli/src/cli.ts new file mode 100644 index 0000000..18a2aa2 --- /dev/null +++ b/apps/cli/src/cli.ts @@ -0,0 +1,3 @@ +import { createBtsCli } from "./index"; + +createBtsCli().run(); diff --git a/apps/cli/src/helpers/project-generation/command-handlers.ts b/apps/cli/src/helpers/project-generation/command-handlers.ts index 6a1e440..19fcffe 100644 --- a/apps/cli/src/helpers/project-generation/command-handlers.ts +++ b/apps/cli/src/helpers/project-generation/command-handlers.ts @@ -1,5 +1,6 @@ import path from "node:path"; import { intro, log, outro } from "@clack/prompts"; +import consola from "consola"; import fs from "fs-extra"; import pc from "picocolors"; import { DEFAULT_CONFIG } from "../../constants"; @@ -7,7 +8,13 @@ import { getAddonsToAdd } from "../../prompts/addons"; import { gatherConfig } from "../../prompts/config-prompts"; import { getProjectName } from "../../prompts/project-name"; import { getDeploymentToAdd } from "../../prompts/web-deploy"; -import type { AddInput, CreateInput, ProjectConfig } from "../../types"; +import type { + AddInput, + CreateInput, + DirectoryConflict, + InitResult, + ProjectConfig, +} from "../../types"; import { trackProjectCreation } from "../../utils/analytics"; import { displayConfig } from "../../utils/display-config"; import { exitWithError, handleError } from "../../utils/errors"; @@ -26,113 +33,221 @@ import { installDependencies } from "./install-dependencies"; export async function createProjectHandler( input: CreateInput & { projectName?: string }, -) { +): Promise { const startTime = Date.now(); + const timeScaffolded = new Date().toISOString(); + + if (input.renderTitle !== false) { + renderTitle(); + } + intro(pc.magenta("Creating a new Better-T Stack project")); + + if (input.yolo) { + consola.fatal("YOLO mode enabled - skipping checks. Things may break!"); + } + + let currentPathInput: string; + if (input.yes && input.projectName) { + currentPathInput = input.projectName; + } else if (input.yes) { + let defaultName = DEFAULT_CONFIG.relativePath; + let counter = 1; + while ( + fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && + fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0 + ) { + defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`; + counter++; + } + currentPathInput = defaultName; + } else { + currentPathInput = await getProjectName(input.projectName); + } + + let finalPathInput: string; + let shouldClearDirectory: boolean; try { - renderTitle(); - intro(pc.magenta("Creating a new Better-T Stack project")); - - let currentPathInput: string; - if (input.yes && input.projectName) { - currentPathInput = input.projectName; - } else if (input.yes) { - let defaultName = DEFAULT_CONFIG.relativePath; - let counter = 1; - while ( - fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && - fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0 - ) { - defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`; - counter++; - } - currentPathInput = defaultName; + if (input.directoryConflict) { + const result = await handleDirectoryConflictProgrammatically( + currentPathInput, + input.directoryConflict, + ); + finalPathInput = result.finalPathInput; + shouldClearDirectory = result.shouldClearDirectory; } else { - currentPathInput = await getProjectName(input.projectName); + const result = await handleDirectoryConflict(currentPathInput); + finalPathInput = result.finalPathInput; + shouldClearDirectory = result.shouldClearDirectory; } + } catch (error) { + const elapsedTimeMs = Date.now() - startTime; + return { + success: false, + projectConfig: { + projectName: "", + projectDir: "", + relativePath: "", + database: "none", + orm: "none", + backend: "none", + runtime: "none", + frontend: [], + addons: [], + examples: [], + auth: false, + git: false, + packageManager: "npm", + install: false, + dbSetup: "none", + api: "none", + webDeploy: "none", + } satisfies ProjectConfig, + reproducibleCommand: "", + timeScaffolded, + elapsedTimeMs, + projectDirectory: "", + relativePath: "", + error: error instanceof Error ? error.message : String(error), + }; + } - const { finalPathInput, shouldClearDirectory } = - await handleDirectoryConflict(currentPathInput); + const { finalResolvedPath, finalBaseName } = await setupProjectDirectory( + finalPathInput, + shouldClearDirectory, + ); - const { finalResolvedPath, finalBaseName } = await setupProjectDirectory( - finalPathInput, - shouldClearDirectory, - ); + const cliInput = { + ...input, + projectDirectory: input.projectName, + }; - const cliInput = { - ...input, - projectDirectory: input.projectName, + const providedFlags = getProvidedFlags(cliInput); + + const flagConfig = processAndValidateFlags( + cliInput, + providedFlags, + finalBaseName, + ); + const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig; + + if (!input.yes && Object.keys(otherFlags).length > 0) { + log.info(pc.yellow("Using these pre-selected options:")); + log.message(displayConfig(otherFlags)); + log.message(""); + } + + let config: ProjectConfig; + if (input.yes) { + config = { + ...DEFAULT_CONFIG, + ...flagConfig, + projectName: finalBaseName, + projectDir: finalResolvedPath, + relativePath: finalPathInput, }; - const providedFlags = getProvidedFlags(cliInput); - const flagConfig = processAndValidateFlags( - cliInput, - providedFlags, - finalBaseName, - ); - const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig; - - if (!input.yes && Object.keys(otherFlags).length > 0) { - log.info(pc.yellow("Using these pre-selected options:")); - log.message(displayConfig(otherFlags)); - log.message(""); + if (config.backend === "convex") { + log.info( + "Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo", + ); + } else if (config.backend === "none") { + log.info( + "Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none", + ); } - let config: ProjectConfig; - if (input.yes) { - config = { - ...DEFAULT_CONFIG, - ...flagConfig, - projectName: finalBaseName, - projectDir: finalResolvedPath, - relativePath: finalPathInput, - }; + log.info(pc.yellow("Using default/flag options (config prompts skipped):")); + log.message(displayConfig(config)); + log.message(""); + } else { + config = await gatherConfig( + flagConfig, + finalBaseName, + finalResolvedPath, + finalPathInput, + ); + } - if (config.backend === "convex") { - log.info( - "Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo", - ); - } else if (config.backend === "none") { - log.info( - "Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none", - ); + await createProject(config); + + const reproducibleCommand = generateReproducibleCommand(config); + log.success( + pc.blue( + `You can reproduce this setup with the following command:\n${reproducibleCommand}`, + ), + ); + + await trackProjectCreation(config); + + const elapsedTimeMs = Date.now() - startTime; + const elapsedTimeInSeconds = (elapsedTimeMs / 1000).toFixed(2); + outro( + pc.magenta( + `Project created successfully in ${pc.bold( + elapsedTimeInSeconds, + )} seconds!`, + ), + ); + + return { + success: true, + projectConfig: config, + reproducibleCommand, + timeScaffolded, + elapsedTimeMs, + projectDirectory: config.projectDir, + relativePath: config.relativePath, + }; +} + +async function handleDirectoryConflictProgrammatically( + currentPathInput: string, + strategy: DirectoryConflict, +): Promise<{ finalPathInput: string; shouldClearDirectory: boolean }> { + const currentPath = path.resolve(process.cwd(), currentPathInput); + + if (!fs.pathExistsSync(currentPath)) { + return { finalPathInput: currentPathInput, shouldClearDirectory: false }; + } + + const dirContents = fs.readdirSync(currentPath); + const isNotEmpty = dirContents.length > 0; + + if (!isNotEmpty) { + return { finalPathInput: currentPathInput, shouldClearDirectory: false }; + } + + switch (strategy) { + case "overwrite": + return { finalPathInput: currentPathInput, shouldClearDirectory: true }; + + case "merge": + return { finalPathInput: currentPathInput, shouldClearDirectory: false }; + + case "increment": { + let counter = 1; + const baseName = currentPathInput; + let finalPathInput = `${baseName}-${counter}`; + + while ( + fs.pathExistsSync(path.resolve(process.cwd(), finalPathInput)) && + fs.readdirSync(path.resolve(process.cwd(), finalPathInput)).length > 0 + ) { + counter++; + finalPathInput = `${baseName}-${counter}`; } - log.info( - pc.yellow("Using default/flag options (config prompts skipped):"), - ); - log.message(displayConfig(config)); - log.message(""); - } else { - config = await gatherConfig( - flagConfig, - finalBaseName, - finalResolvedPath, - finalPathInput, - ); + return { finalPathInput, shouldClearDirectory: false }; } - await createProject(config); + case "error": + throw new Error( + `Directory "${currentPathInput}" already exists and is not empty. Use directoryConflict: "overwrite", "merge", or "increment" to handle this.`, + ); - const reproducibleCommand = generateReproducibleCommand(config); - log.success( - pc.blue( - `You can reproduce this setup with the following command:\n${reproducibleCommand}`, - ), - ); - - await trackProjectCreation(config); - - const elapsedTimeInSeconds = ((Date.now() - startTime) / 1000).toFixed(2); - outro( - pc.magenta( - `Project created successfully in ${pc.bold( - elapsedTimeInSeconds, - )} seconds!`, - ), - ); - } catch (error) { - handleError(error, "Failed to create project"); + default: + throw new Error(`Unknown directory conflict strategy: ${strategy}`); } } diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 66a52fd..1b12d04 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -7,17 +7,35 @@ import { createProjectHandler, } from "./helpers/project-generation/command-handlers"; import { + type AddInput, + type Addons, AddonsSchema, + type API, APISchema, + type Backend, BackendSchema, + type BetterTStackConfig, + type CreateInput, + type Database, DatabaseSchema, + type DatabaseSetup, DatabaseSetupSchema, + type DirectoryConflict, + DirectoryConflictSchema, + type Examples, ExamplesSchema, + type Frontend, FrontendSchema, + type InitResult, + type ORM, ORMSchema, + type PackageManager, PackageManagerSchema, + type ProjectConfig, ProjectNameSchema, + type Runtime, RuntimeSchema, + type WebDeploy, WebDeploySchema, } from "./types"; import { handleError } from "./utils/errors"; @@ -28,7 +46,7 @@ import { displaySponsors, fetchSponsors } from "./utils/sponsors"; const t = trpcServer.initTRPC.create(); -const router = t.router({ +export const router = t.router({ init: t.procedure .meta({ description: "Create a new Better-T Stack project", @@ -44,6 +62,18 @@ const router = t.router({ .optional() .default(false) .describe("Use default configuration"), + yolo: z + .boolean() + .optional() + .default(false) + .describe( + "(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks", + ), + verbose: z + .boolean() + .optional() + .default(false) + .describe("Show detailed result information"), database: DatabaseSchema.optional(), orm: ORMSchema.optional(), auth: z.boolean().optional(), @@ -58,6 +88,8 @@ const router = t.router({ runtime: RuntimeSchema.optional(), api: APISchema.optional(), webDeploy: WebDeploySchema.optional(), + directoryConflict: DirectoryConflictSchema.optional(), + renderTitle: z.boolean().optional(), }), ]), ) @@ -67,7 +99,11 @@ const router = t.router({ projectName, ...options, }; - await createProjectHandler(combinedInput); + const result = await createProjectHandler(combinedInput); + + if (options.verbose) { + return result; + } }), add: t.procedure .meta({ @@ -129,8 +165,90 @@ const router = t.router({ }), }); -createCli({ - router, - name: "create-better-t-stack", - version: getLatestCLIVersion(), -}).run(); +const caller = t.createCallerFactory(router)({}); + +export function createBtsCli() { + return createCli({ + router, + name: "create-better-t-stack", + version: getLatestCLIVersion(), + }); +} + +/** + * Initialize a new Better-T Stack project + * + * @example CLI usage: + * ```bash + * npx create-better-t-stack my-app --yes + * ``` + * + * @example Programmatic usage (always returns structured data): + * ```typescript + * import { init } from "create-better-t-stack"; + * + * const result = await init("my-app", { + * yes: true, + * frontend: ["tanstack-router"], + * backend: "hono", + * database: "sqlite", + * orm: "drizzle", + * auth: true, + * addons: ["biome", "turborepo"], + * packageManager: "bun", + * install: false, + * directoryConflict: "increment", // auto-handle conflicts + * }); + * + * if (result.success) { + * console.log(`Project created at: ${result.projectDirectory}`); + * console.log(`Reproducible command: ${result.reproducibleCommand}`); + * console.log(`Time taken: ${result.elapsedTimeMs}ms`); + * } + * ``` + */ +export async function init( + projectName?: string, + options?: CreateInput, +): Promise { + const opts = (options ?? {}) as CreateInput; + const programmaticOpts = { ...opts, verbose: true }; + const prev = process.env.BTS_PROGRAMMATIC; + process.env.BTS_PROGRAMMATIC = "1"; + const result = await caller.init([projectName, programmaticOpts]); + if (prev === undefined) delete process.env.BTS_PROGRAMMATIC; + else process.env.BTS_PROGRAMMATIC = prev; + return result as InitResult; +} + +export async function sponsors() { + return caller.sponsors(); +} + +export async function docs() { + return caller.docs(); +} + +export async function builder() { + return caller.builder(); +} + +export type { + Database, + ORM, + Backend, + Runtime, + Frontend, + Addons, + Examples, + PackageManager, + DatabaseSetup, + API, + WebDeploy, + DirectoryConflict, + CreateInput, + AddInput, + ProjectConfig, + BetterTStackConfig, + InitResult, +}; diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index ed65b20..caee699 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -108,9 +108,16 @@ export const WebDeploySchema = z .describe("Web deployment"); export type WebDeploy = z.infer; +export const DirectoryConflictSchema = z + .enum(["merge", "overwrite", "increment", "error"]) + .describe("How to handle existing directory conflicts"); +export type DirectoryConflict = z.infer; + export type CreateInput = { projectName?: string; yes?: boolean; + yolo?: boolean; + verbose?: boolean; database?: Database; orm?: ORM; auth?: boolean; @@ -125,6 +132,8 @@ export type CreateInput = { runtime?: Runtime; api?: API; webDeploy?: WebDeploy; + directoryConflict?: DirectoryConflict; + renderTitle?: boolean; }; export type AddInput = { @@ -175,3 +184,14 @@ export interface BetterTStackConfig { api: API; webDeploy: WebDeploy; } + +export interface InitResult { + success: boolean; + projectConfig: ProjectConfig; + reproducibleCommand: string; + timeScaffolded: string; + elapsedTimeMs: number; + projectDirectory: string; + relativePath: string; + error?: string; +} diff --git a/apps/cli/src/utils/errors.ts b/apps/cli/src/utils/errors.ts index 6afad34..83559ea 100644 --- a/apps/cli/src/utils/errors.ts +++ b/apps/cli/src/utils/errors.ts @@ -2,13 +2,23 @@ import { cancel } from "@clack/prompts"; import consola from "consola"; import pc from "picocolors"; +function isProgrammatic(): boolean { + return process.env.BTS_PROGRAMMATIC === "1"; +} + export function exitWithError(message: string): never { consola.error(pc.red(message)); + if (isProgrammatic()) { + throw new Error(message); + } process.exit(1); } export function exitCancelled(message = "Operation cancelled"): never { cancel(pc.red(message)); + if (isProgrammatic()) { + throw new Error(message); + } process.exit(0); } @@ -16,5 +26,8 @@ export function handleError(error: unknown, fallbackMessage?: string): never { const message = error instanceof Error ? error.message : fallbackMessage || String(error); consola.error(pc.red(message)); + if (isProgrammatic()) { + throw new Error(message); + } process.exit(1); } diff --git a/apps/cli/src/utils/project-directory.ts b/apps/cli/src/utils/project-directory.ts index 4ae02a4..1b733e4 100644 --- a/apps/cli/src/utils/project-directory.ts +++ b/apps/cli/src/utils/project-directory.ts @@ -7,6 +7,7 @@ import { exitCancelled, handleError } from "./errors"; export async function handleDirectoryConflict( currentPathInput: string, + silent = false, ): Promise<{ finalPathInput: string; shouldClearDirectory: boolean; @@ -20,6 +21,12 @@ export async function handleDirectoryConflict( return { finalPathInput: currentPathInput, shouldClearDirectory: false }; } + if (silent) { + throw new Error( + `Directory "${currentPathInput}" already exists and is not empty. In silent mode, please provide a different project name or clear the directory manually.`, + ); + } + log.warn( `Directory "${pc.yellow( currentPathInput, diff --git a/apps/cli/src/validation.ts b/apps/cli/src/validation.ts index 177b02f..b04dc29 100644 --- a/apps/cli/src/validation.ts +++ b/apps/cli/src/validation.ts @@ -1,13 +1,10 @@ import path from "node:path"; import { - type Addons, type API, type Backend, type CLIInput, type Database, type DatabaseSetup, - type Examples, - type Frontend, type ORM, type PackageManager, type ProjectConfig, @@ -28,6 +25,36 @@ import { } from "./utils/compatibility-rules"; import { exitWithError } from "./utils/errors"; +function processArrayOption(options: (T | "none")[] | undefined): T[] { + if (!options || options.length === 0) return []; + if (options.includes("none" as T | "none")) return []; + return options.filter((item): item is T => item !== "none"); +} + +function deriveProjectName( + projectName?: string, + projectDirectory?: string, +): string { + if (projectName) { + return projectName; + } + if (projectDirectory) { + return path.basename(path.resolve(process.cwd(), projectDirectory)); + } + return ""; +} + +function validateProjectName(name: string): void { + const result = ProjectNameSchema.safeParse(name); + if (!result.success) { + exitWithError( + `Invalid project name: ${ + result.error.issues[0]?.message || "Invalid project name" + }`, + ); + } +} + export function processAndValidateFlags( options: CLIInput, providedFlags: Set, @@ -96,29 +123,13 @@ export function processAndValidateFlags( config.webDeploy = options.webDeploy as WebDeploy; } - if (projectName) { - const result = ProjectNameSchema.safeParse(path.basename(projectName)); - if (!result.success) { - exitWithError( - `Invalid project name: ${ - result.error.issues[0]?.message || "Invalid project name" - }`, - ); - } - config.projectName = projectName; - } else if (options.projectDirectory) { - const baseName = path.basename( - path.resolve(process.cwd(), options.projectDirectory), - ); - const result = ProjectNameSchema.safeParse(baseName); - if (!result.success) { - exitWithError( - `Invalid project name: ${ - result.error.issues[0]?.message || "Invalid project name" - }`, - ); - } - config.projectName = baseName; + const derivedName = deriveProjectName(projectName, options.projectDirectory); + if (derivedName) { + const nameToValidate = projectName + ? path.basename(projectName) + : derivedName; + validateProjectName(nameToValidate); + config.projectName = projectName || derivedName; } if (options.frontend && options.frontend.length > 0) { @@ -128,9 +139,7 @@ export function processAndValidateFlags( } config.frontend = []; } else { - const validOptions = options.frontend.filter( - (f): f is Frontend => f !== "none", - ); + const validOptions = processArrayOption(options.frontend); ensureSingleWebAndNative(validOptions); config.frontend = validOptions; } @@ -152,9 +161,7 @@ export function processAndValidateFlags( } config.addons = []; } else { - config.addons = options.addons.filter( - (addon): addon is Addons => addon !== "none", - ); + config.addons = processArrayOption(options.addons); } } if (options.examples && options.examples.length > 0) { @@ -164,9 +171,7 @@ export function processAndValidateFlags( } config.examples = []; } else { - config.examples = options.examples.filter( - (ex): ex is Examples => ex !== "none", - ); + config.examples = processArrayOption(options.examples); if (options.examples.includes("none") && config.backend !== "convex") { config.examples = []; } @@ -421,6 +426,85 @@ export function validateConfigCompatibility(config: Partial) { ); } +export function processProvidedFlagsWithoutValidation( + options: CLIInput, + projectName?: string, +): Partial { + const config: Partial = {}; + + if (options.api) { + config.api = options.api as API; + } + + if (options.backend) { + config.backend = options.backend as Backend; + } + + if (options.database) { + config.database = options.database as Database; + } + + if (options.orm) { + config.orm = options.orm as ORM; + } + + if (options.auth !== undefined) { + config.auth = options.auth; + } + + if (options.git !== undefined) { + config.git = options.git; + } + + if (options.install !== undefined) { + config.install = options.install; + } + + if (options.runtime) { + config.runtime = options.runtime as Runtime; + } + + if (options.dbSetup) { + config.dbSetup = options.dbSetup as DatabaseSetup; + } + + if (options.packageManager) { + config.packageManager = options.packageManager as PackageManager; + } + + if (options.webDeploy) { + config.webDeploy = options.webDeploy as WebDeploy; + } + + const derivedName = deriveProjectName(projectName, options.projectDirectory); + if (derivedName) { + const nameToValidate = projectName + ? path.basename(projectName) + : derivedName; + const result = ProjectNameSchema.safeParse(nameToValidate); + if (!result.success) { + throw new Error( + `Invalid project name: ${result.error.issues[0]?.message}`, + ); + } + config.projectName = projectName || derivedName; + } + + if (options.frontend && options.frontend.length > 0) { + config.frontend = processArrayOption(options.frontend); + } + + if (options.addons && options.addons.length > 0) { + config.addons = processArrayOption(options.addons); + } + + if (options.examples && options.examples.length > 0) { + config.examples = processArrayOption(options.examples); + } + + return config; +} + export function getProvidedFlags(options: CLIInput): Set { return new Set( Object.keys(options).filter( diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts index 5565b33..b2b8919 100644 --- a/apps/cli/test/cli.smoke.test.ts +++ b/apps/cli/test/cli.smoke.test.ts @@ -9,9 +9,33 @@ import { removeSync, } from "fs-extra"; import * as JSONC from "jsonc-parser"; +import { FailedToExitError } from "trpc-cli"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { createBtsCli } from "../src/index"; -const CLI_BIN = join(__dirname, "..", "dist", "index.js"); +async function runCli(argv: string[], cwd: string) { + const previous = process.cwd(); + process.chdir(cwd); + try { + const cli = createBtsCli(); + await cli + .run({ + argv, + logger: { info: () => {}, error: () => {} }, + process: { exit: () => void 0 as never }, + }) + .catch((err) => { + let e: unknown = err; + while (e instanceof FailedToExitError) { + if (e.exitCode === 0) return e.cause; + e = e.cause; + } + throw e; + }); + } finally { + process.chdir(previous); + } +} function createTmpDir(_prefix: string) { const dir = join(__dirname, "..", ".smoke"); @@ -22,39 +46,30 @@ function createTmpDir(_prefix: string) { return dir; } -async function runCli(args: string[], cwd: string, env?: NodeJS.ProcessEnv) { - const subprocess = execa("node", [CLI_BIN, ...args], { - cwd, - env: { - ...process.env, - BTS_TELEMETRY_DISABLED: "1", - ...env, - }, - }); - subprocess.stdout?.pipe(process.stdout); - subprocess.stderr?.pipe(process.stderr); - const { exitCode } = await subprocess; - expect(exitCode).toBe(0); -} - -async function runCliExpectingError( - args: string[], - cwd: string, - env?: NodeJS.ProcessEnv, -) { - const subprocess = execa("node", [CLI_BIN, ...args], { - cwd, - env: { - ...process.env, - BTS_TELEMETRY_DISABLED: "1", - ...env, - }, - reject: false, - }); - subprocess.stdout?.pipe(process.stdout); - subprocess.stderr?.pipe(process.stderr); - const { exitCode } = await subprocess; - expect(exitCode).not.toBe(0); +async function runCliExpectingError(args: string[], cwd: string) { + const previous = process.cwd(); + process.chdir(cwd); + try { + const cli = createBtsCli(); + let threw = false; + await cli + .run({ + argv: args, + logger: { info: () => {}, error: () => {} }, + process: { exit: () => void 0 as never }, + }) + .catch((err) => { + threw = true; + let e: unknown = err; + while (e instanceof FailedToExitError) { + if (e.exitCode === 0) throw new Error("Expected failure"); + e = e.cause; + } + }); + expect(threw).toBe(true); + } finally { + process.chdir(previous); + } } function assertScaffoldedProject(dir: string) { @@ -310,9 +325,8 @@ describe("create-better-t-stack smoke", () => { expect(exitCode).toBe(0); consola.success("CLI build completed"); - const cliBinExists = existsSync(CLI_BIN); - expect(cliBinExists).toBe(true); - consola.info(`CLI binary: ${CLI_BIN}`); + process.env.BTS_TELEMETRY_DISABLED = "1"; + consola.info("Programmatic CLI mode"); }); // Exhaustive matrix: all frontends x standard backends (no db, no orm, no api, no auth) diff --git a/apps/cli/test/programmatic-api.test.ts b/apps/cli/test/programmatic-api.test.ts new file mode 100644 index 0000000..5fecf98 --- /dev/null +++ b/apps/cli/test/programmatic-api.test.ts @@ -0,0 +1,340 @@ +import { join } from "node:path"; +import { ensureDirSync, existsSync, readFileSync, removeSync } from "fs-extra"; +import { parse as parseJsonc } from "jsonc-parser"; +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { init } from "../src/index"; +import type { BetterTStackConfig } from "../src/types"; + +let testCounter = 0; +let tmpDir: string; +let originalCwd: string; + +function createTmpDir() { + testCounter++; + const dir = join(__dirname, "..", `.prog-test-${testCounter}`); + if (existsSync(dir)) { + removeSync(dir); + } + ensureDirSync(dir); + return dir; +} + +function assertProjectExists(dir: string) { + expect(existsSync(dir)).toBe(true); + expect(existsSync(join(dir, "package.json"))).toBe(true); + expect(existsSync(join(dir, "bts.jsonc"))).toBe(true); +} + +function assertBtsConfig( + dir: string, + expectedConfig: Partial<{ + frontend: string[]; + backend: string; + database: string; + orm: string; + api: string; + runtime: string; + addons: string[]; + }>, +) { + const configPath = join(dir, "bts.jsonc"); + expect(existsSync(configPath)).toBe(true); + + const configContent = readFileSync(configPath, "utf-8"); + const config: BetterTStackConfig = parseJsonc(configContent); + + if (expectedConfig.frontend) { + expect(config.frontend).toEqual(expectedConfig.frontend); + } + if (expectedConfig.backend) { + expect(config.backend).toBe(expectedConfig.backend); + } + if (expectedConfig.database) { + expect(config.database).toBe(expectedConfig.database); + } + if (expectedConfig.orm) { + expect(config.orm).toBe(expectedConfig.orm); + } + if (expectedConfig.api) { + expect(config.api).toBe(expectedConfig.api); + } + if (expectedConfig.runtime) { + expect(config.runtime).toBe(expectedConfig.runtime); + } + if (expectedConfig.addons) { + expect(config.addons).toEqual(expectedConfig.addons); + } +} + +describe("Programmatic API - Fast Tests", () => { + beforeEach(() => { + originalCwd = process.cwd(); + tmpDir = createTmpDir(); + process.chdir(tmpDir); + }); + + afterEach(() => { + process.chdir(originalCwd); + if (existsSync(tmpDir)) { + removeSync(tmpDir); + } + }); + + describe("Core functionality", () => { + test("creates minimal project successfully", async () => { + const result = await init("test-app", { + yes: true, + install: false, + git: false, + }); + + expect(result.success).toBe(true); + expect(result.projectConfig.projectName).toBe("test-app"); + expect(result.projectDirectory).toContain("test-app"); + expect(result.reproducibleCommand).toContain("test-app"); + expect(typeof result.elapsedTimeMs).toBe("number"); + expect(result.elapsedTimeMs).toBeGreaterThan(0); + + assertProjectExists(result.projectDirectory); + }, 15000); + + test("returns complete result structure", async () => { + const result = await init("result-test", { + yes: true, + install: false, + git: false, + }); + + expect(result).toHaveProperty("success"); + expect(result).toHaveProperty("projectConfig"); + expect(result).toHaveProperty("reproducibleCommand"); + expect(result).toHaveProperty("timeScaffolded"); + expect(result).toHaveProperty("elapsedTimeMs"); + expect(result).toHaveProperty("projectDirectory"); + expect(result).toHaveProperty("relativePath"); + }, 15000); + + test("handles project with custom name", async () => { + const result = await init("custom-name", { + yes: true, + install: false, + git: false, + }); + + expect(result.success).toBe(true); + expect(result.projectConfig.projectName).toBe("custom-name"); + expect(result.projectDirectory).toContain("custom-name"); + }, 15000); + }); + + describe("Configuration options", () => { + test("creates project with Next.js frontend", async () => { + const result = await init("next-app", { + yes: true, + frontend: ["next"], + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + frontend: ["next"], + }); + }, 15000); + + test("creates project with Fastify backend", async () => { + const result = await init("fastify-app", { + yes: true, + backend: "fastify", + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + backend: "fastify", + }); + }, 15000); + + test("creates project with PostgreSQL + Prisma", async () => { + const result = await init("pg-app", { + yes: true, + database: "postgres", + orm: "prisma", + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + database: "postgres", + orm: "prisma", + }); + }, 15000); + + test("creates project with oRPC API", async () => { + const result = await init("orpc-app", { + yes: true, + api: "orpc", + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + api: "orpc", + }); + }, 15000); + + test("creates project with Node runtime", async () => { + const result = await init("node-app", { + yes: true, + runtime: "node", + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + runtime: "node", + }); + }, 15000); + + test("creates project with Biome addon", async () => { + const result = await init("biome-app", { + yes: true, + addons: ["biome"], + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + addons: ["biome"], + }); + }, 15000); + }); + + describe("Error scenarios", () => { + test("handles invalid project name", async () => { + await expect( + init("", { + yes: true, + install: false, + git: false, + }), + ).rejects.toThrow("Project name cannot be empty"); + }); + + test("handles invalid characters in project name", async () => { + await expect( + init("invalid", { + yes: true, + install: false, + git: false, + }), + ).rejects.toThrow("invalid characters"); + }); + + test("handles incompatible database + ORM combination", async () => { + await expect( + init("incompatible", { + yes: true, + database: "mongodb", + orm: "drizzle", + install: false, + git: false, + yolo: false, + }), + ).rejects.toThrow(/requires Mongoose or Prisma/); + }); + + test("handles auth without database", async () => { + await expect( + init("auth-no-db", { + yes: true, + auth: true, + database: "none", + install: false, + git: false, + yolo: false, + }), + ).rejects.toThrow(/Authentication requires/); + }); + + test("handles directory conflict with error strategy", async () => { + const result1 = await init("conflict-test", { + yes: true, + install: false, + git: false, + }); + + expect(result1.success).toBe(true); + + const result2 = await init("conflict-test", { + yes: true, + install: false, + git: false, + directoryConflict: "error", + }); + + expect(result2.success).toBe(false); + expect(result2.error).toMatch(/already exists/); + }, 20000); + }); + + describe("Advanced features", () => { + test("creates project with multiple addons", async () => { + const result = await init("multi-addon", { + yes: true, + addons: ["biome", "turborepo"], + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + addons: ["biome", "turborepo"], + }); + }, 15000); + + test("creates project with authentication enabled", async () => { + const result = await init("auth-app", { + yes: true, + auth: true, + database: "sqlite", + orm: "drizzle", + install: false, + git: false, + }); + + expect(result.success).toBe(true); + assertBtsConfig(result.projectDirectory, { + database: "sqlite", + orm: "drizzle", + }); + expect(result.projectConfig.auth).toBe(true); + }, 15000); + + test("validates reproducible command format", async () => { + const result = await init("repro-test", { + yes: true, + frontend: ["next"], + backend: "fastify", + database: "postgres", + orm: "prisma", + install: false, + git: false, + }); + + expect(result.success).toBe(true); + expect(result.reproducibleCommand).toContain("repro-test"); + expect(result.reproducibleCommand).toContain("--frontend next"); + expect(result.reproducibleCommand).toContain("--backend fastify"); + expect(result.reproducibleCommand).toContain("--database postgres"); + expect(result.reproducibleCommand).toContain("--orm prisma"); + expect(result.reproducibleCommand).toContain("--no-install"); + expect(result.reproducibleCommand).toContain("--no-git"); + }, 15000); + }); +}); diff --git a/apps/cli/tsdown.config.ts b/apps/cli/tsdown.config.ts index c4210ce..3c1d093 100644 --- a/apps/cli/tsdown.config.ts +++ b/apps/cli/tsdown.config.ts @@ -1,11 +1,12 @@ import { defineConfig } from "tsdown"; export default defineConfig({ - entry: ["src/index.ts"], + entry: ["src/index.ts", "src/cli.ts"], format: ["esm"], clean: true, shims: true, outDir: "dist", + dts: true, outputOptions: { banner: "#!/usr/bin/env node", }, diff --git a/apps/web/content/docs/cli/index.mdx b/apps/web/content/docs/cli/index.mdx index a25ce37..b423509 100644 --- a/apps/web/content/docs/cli/index.mdx +++ b/apps/web/content/docs/cli/index.mdx @@ -22,6 +22,8 @@ create-better-t-stack [project-directory] [options] ### Key Options - `--yes, -y`: Use default configuration (skips prompts) +- `--verbose`: Show detailed result information as JSON +- `--yolo`: Bypass validations and compatibility checks - `--package-manager `: `npm`, `pnpm`, `bun` - `--install / --no-install`: Install dependencies after creation - `--git / --no-git`: Initialize Git repository @@ -34,6 +36,8 @@ create-better-t-stack [project-directory] [options] - `--db-setup `: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `mongodb-atlas`, `docker` - `--examples `: `none`, `todo`, `ai` - `--web-deploy `: `none`, `workers` +- `--directory-conflict `: `merge`, `overwrite`, `increment`, `error` +- `--render-title / --no-render-title`: Show/hide ASCII art title See the full reference in [Options](/docs/cli/options). @@ -147,3 +151,35 @@ create-better-t-stack api-server \ cd my-existing-project create-better-t-stack add --addons tauri starlight --install ``` + +## Programmatic Usage + +For advanced use cases, automation, or integration with other tools, you can use the [Programmatic API](/docs/cli/programmatic-api) to create projects from Node.js code: + +```typescript +import { init } from "create-better-t-stack"; + +// Create multiple projects programmatically +const projects = [ + { name: "api", config: { frontend: ["none"], backend: "hono" } }, + { name: "web", config: { frontend: ["next"], backend: "none" } } +]; + +for (const { name, config } of projects) { + const result = await init(name, { + yes: true, + ...config, + directoryConflict: "increment" + }); + + console.log(result.success ? `✅ ${name}` : `❌ ${name}: ${result.error}`); +} +``` + +This is useful for: +- **Build tools and generators** - Create projects from templates +- **CI/CD pipelines** - Generate test projects automatically +- **Development workflows** - Batch create related projects +- **Custom tooling** - Integrate with your existing development setup + +See the [Programmatic API documentation](/docs/cli/programmatic-api) for complete examples and API reference. diff --git a/apps/web/content/docs/cli/meta.json b/apps/web/content/docs/cli/meta.json index f346b29..bd78e7b 100644 --- a/apps/web/content/docs/cli/meta.json +++ b/apps/web/content/docs/cli/meta.json @@ -1,5 +1,5 @@ { "title": "CLI", "defaultOpen": true, - "pages": ["index", "options", "compatibility"] + "pages": ["index", "programmatic-api", "options", "compatibility"] } diff --git a/apps/web/content/docs/cli/options.mdx b/apps/web/content/docs/cli/options.mdx index c6f3054..9e891b2 100644 --- a/apps/web/content/docs/cli/options.mdx +++ b/apps/web/content/docs/cli/options.mdx @@ -37,6 +37,48 @@ Control Git repository initialization. create-better-t-stack --no-git ``` +### `--yolo` + +Bypass validations and compatibility checks. Not recommended for normal use. + +```bash +create-better-t-stack --yolo +``` + +### `--verbose` + +Show detailed result information in JSON format after project creation. + +```bash +create-better-t-stack --verbose +``` + +### `--render-title / --no-render-title` + +Control whether the ASCII art title is shown. Enabled by default. + +```bash +# Hide the title (useful in CI) +create-better-t-stack --no-render-title +``` + +### `--directory-conflict ` + +How to handle existing, non-empty target directories: + +- `merge`: Keep existing files and merge new ones +- `overwrite`: Clear the directory before scaffolding +- `increment`: Create a suffixed directory (e.g., `my-app-1`) +- `error`: Fail instead of prompting + +```bash +# Overwrite an existing directory without prompting +create-better-t-stack my-app --yes --directory-conflict overwrite + +# Safely create a new directory name if it exists +create-better-t-stack my-app --yes --directory-conflict increment +``` + ## Database Options ### `--database ` diff --git a/apps/web/content/docs/cli/programmatic-api.mdx b/apps/web/content/docs/cli/programmatic-api.mdx new file mode 100644 index 0000000..85434a5 --- /dev/null +++ b/apps/web/content/docs/cli/programmatic-api.mdx @@ -0,0 +1,276 @@ +--- +title: Programmatic API +description: Use Better-T Stack programmatically in your Node.js applications +--- + +## Overview + +The Better-T Stack CLI can be used programmatically in your Node.js applications, allowing you to create projects, add features, and manage configurations through JavaScript/TypeScript code instead of shell commands. + +## Installation + +Install the package in your Node.js project: + +```bash +npm install create-better-t-stack +# or +pnpm add create-better-t-stack +# or +bun add create-better-t-stack +``` + +## Quick Start + +### Basic Project Creation + +```typescript +import { init } from "create-better-t-stack"; + +async function createProject() { + const result = await init("my-app", { + yes: true, // Use defaults, no prompts + frontend: ["tanstack-router"], + backend: "hono", + database: "sqlite", + orm: "drizzle", + auth: true, + packageManager: "bun", + install: false, // Don't install deps automatically + }); + + if (result.success) { + console.log(`✅ Project created at: ${result.projectDirectory}`); + console.log(`📝 Reproducible command: ${result.reproducibleCommand}`); + console.log(`⏱️ Time taken: ${result.elapsedTimeMs}ms`); + } else { + console.error(`❌ Failed: ${result.error}`); + } +} + +createProject(); +``` + +### Directory Conflict Handling + +```typescript +import { init } from "create-better-t-stack"; + +const result = await init("existing-folder", { + yes: true, + directoryConflict: "increment", // Creates "existing-folder-1" + renderTitle: false, // Hide ASCII art +}); +``` + +## API Reference + +### `init(projectName?, options?)` + +Creates a new Better-T Stack project. + +**Parameters:** +- `projectName` (string, optional): Project name or directory path +- `options` (CreateInput, optional): Configuration options + +**Returns:** `Promise` + +**Example:** +```typescript +const result = await init("my-project", { + frontend: ["next"], + backend: "hono", + database: "postgres", + orm: "drizzle" +}); +``` + +### `sponsors()` + +Display Better-T Stack sponsors (same as CLI). + +**Returns:** `Promise` + +### `docs()` + +Open documentation in browser (same as CLI). + +**Returns:** `Promise` + +### `builder()` + +Open web-based stack builder (same as CLI). + +**Returns:** `Promise` + +## Type Definitions + +### `CreateInput` + +Configuration options for project creation: + +```typescript +interface CreateInput { + projectName?: string; + yes?: boolean; // Skip prompts, use defaults + yolo?: boolean; // Bypass validations (not recommended) + verbose?: boolean; // Show JSON result (CLI only, programmatic always returns result) + database?: Database; // "none" | "sqlite" | "postgres" | "mysql" | "mongodb" + orm?: ORM; // "none" | "drizzle" | "prisma" | "mongoose" + auth?: boolean; // Include authentication + frontend?: Frontend[]; // Array of frontend frameworks + addons?: Addons[]; // Array of addons + examples?: Examples[]; // Array of examples + git?: boolean; // Initialize git repo + packageManager?: PackageManager; // "npm" | "pnpm" | "bun" + install?: boolean; // Install dependencies + dbSetup?: DatabaseSetup; // Database hosting setup + backend?: Backend; // Backend framework + runtime?: Runtime; // Runtime environment + api?: API; // API type + webDeploy?: WebDeploy; // Web deployment setup + directoryConflict?: DirectoryConflict; // "merge" | "overwrite" | "increment" | "error" + renderTitle?: boolean; // Show ASCII art title +} +``` + +### `InitResult` + +Result object returned by `init()`: + +```typescript +interface InitResult { + success: boolean; // Whether operation succeeded + projectConfig: ProjectConfig; // Final project configuration + reproducibleCommand: string; // CLI command to recreate project + timeScaffolded: string; // ISO timestamp of creation + elapsedTimeMs: number; // Time taken in milliseconds + projectDirectory: string; // Absolute path to project + relativePath: string; // Relative path to project + error?: string; // Error message if failed +} +``` + +## Configuration Options + +### Directory Conflict Resolution + +Control how existing directories are handled: + +```typescript +// Merge with existing files (careful with conflicts) +directoryConflict: "merge" + +// Completely overwrite existing directory +directoryConflict: "overwrite" + +// Create new directory with incremented name (my-app-1) +directoryConflict: "increment" + +// Throw error if directory exists +directoryConflict: "error" +``` + +### Title Rendering + +Control CLI output appearance: + +```typescript +// Hide ASCII art title (useful for automated scripts) +renderTitle: false + +// Show ASCII art title (default) +renderTitle: true +``` + +### YOLO Mode + +Bypass validation checks (not recommended): + +```typescript +// Skip compatibility validations +yolo: true +``` + +## Error Handling + +The programmatic API uses different error handling than the CLI: + +### Directory Conflicts + +Directory conflict errors return structured results instead of throwing: + +```typescript +const result = await init("existing-dir", { + directoryConflict: "error" +}); + +if (!result.success) { + console.log(result.error); // "Directory exists and is not empty..." + // Handle gracefully instead of process exit +} +``` + +### Validation Errors + +Validation errors still throw exceptions (maintains CLI compatibility): + +```typescript +try { + await init("test", { + database: "mongodb", + orm: "drizzle" // Invalid combination + }); +} catch (error) { + console.error(error.message); // "MongoDB requires Mongoose or Prisma ORM" +} +``` + +## Migration from CLI + +### CLI Command to Programmatic API + +Convert CLI commands to programmatic calls: + +```bash +# CLI Command +create-better-t-stack my-app \ + --frontend tanstack-router \ + --backend hono \ + --database postgres \ + --orm drizzle \ + --auth \ + --yes +``` + +```typescript +// Programmatic Equivalent +const result = await init("my-app", { + frontend: ["tanstack-router"], + backend: "hono", + database: "postgres", + orm: "drizzle", + auth: true, + yes: true +}); +``` + +### Handling Prompts + +CLI prompts become explicit options: + +```typescript +// Instead of interactive prompts, specify all options +const result = await init("my-app", { + yes: true, // Skip all prompts + frontend: ["tanstack-router"], + backend: "hono", + database: "postgres", + orm: "drizzle", + auth: true, + addons: ["biome", "turborepo"], + examples: ["todo"], + packageManager: "bun", + install: false, + git: true +}); +``` diff --git a/bun.lock b/bun.lock index 73ff139..62a3bd7 100644 --- a/bun.lock +++ b/bun.lock @@ -16,7 +16,7 @@ "name": "create-better-t-stack", "version": "2.31.0", "bin": { - "create-better-t-stack": "dist/index.js", + "create-better-t-stack": "dist/cli.js", }, "dependencies": { "@clack/prompts": "^0.11.0", @@ -30,12 +30,12 @@ "picocolors": "^1.1.1", "trpc-cli": "^0.10.2", "ts-morph": "^26.0.0", - "zod": "^4.0.15", + "zod": "^4.0.17", }, "devDependencies": { "@types/fs-extra": "^11.0.4", - "@types/node": "^24.2.0", - "tsdown": "^0.13.3", + "@types/node": "^24.2.1", + "tsdown": "^0.14.1", "typescript": "^5.9.2", "vitest": "^3.2.4", }, @@ -484,7 +484,7 @@ "@mdx-js/mdx": ["@mdx-js/mdx@3.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.1", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@tybys/wasm-util": "^0.10.0" } }, "sha512-KVlQ/jgywZpixGCKMNwxStmmbYEMyokZpCf2YuIChhfJA2uqfAKNEM8INz7zzTo55iEXfBhIIs3VqYyqzDLj8g=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.3", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@tybys/wasm-util": "^0.10.0" } }, "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q=="], "@next/env": ["@next/env@15.3.5", "", {}, "sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g=="], @@ -586,9 +586,9 @@ "@orama/orama": ["@orama/orama@3.1.11", "", {}, "sha512-Szki0cgFiXE5F9RLx2lUyEtJllnuCSQ4B8RLDwIjXkVit6qZjoDAxH+xhJs29MjKLDz0tbPLdKFa6QrQ/qoGGA=="], - "@oxc-project/runtime": ["@oxc-project/runtime@0.80.0", "", {}, "sha512-3rzy1bJAZ4s7zV9TKT60x119RwJDCDqEtCwK/Zc2qlm7wGhiIUxLLYUhE/mN91yB0u1kxm5sh4NjU12sPqQTpg=="], + "@oxc-project/runtime": ["@oxc-project/runtime@0.81.0", "", {}, "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw=="], - "@oxc-project/types": ["@oxc-project/types@0.80.0", "", {}, "sha512-xxHQm8wfCv2e8EmtaDwpMeAHOWqgQDAYg+BJouLXSQt5oTKu9TIXrgNMGSrM2fLvKmECsRd9uUFAAD+hPyootA=="], + "@oxc-project/types": ["@oxc-project/types@0.81.0", "", {}, "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA=="], "@poppinss/colors": ["@poppinss/colors@4.1.5", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="], @@ -718,35 +718,35 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.31", "", { "os": "android", "cpu": "arm64" }, "sha512-0mFtKwOG7smn0HkvQ6h8j0m/ohkR7Fp5eMTJ2Pns/HSbePHuDpxMaQ4TjZ6arlVXxpeWZlAHeT5BeNsOA3iWTg=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.32", "", { "os": "android", "cpu": "arm64" }, "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.31", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BHfHJ8Nb5G7ZKJl6pimJacupONT4F7w6gmQHw41rouAnJF51ORDwGefWeb6OMLzGmJwzxlIVPERfnJf1EsMM7A=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.32", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.31", "", { "os": "darwin", "cpu": "x64" }, "sha512-4MiuRtExC08jHbSU/diIL+IuQP+3Ck1FbWAplK+ysQJ7fxT3DMxy5FmnIGfmhaqow8oTjb2GEwZJKgTRjZL1Vw=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.32", "", { "os": "darwin", "cpu": "x64" }, "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.31", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nffC1u7ccm12qlAea8ExY3AvqlaHy/o/3L4p5Es8JFJ3zJSs6e3DyuxGZZVdl9EVwsLxPPTvioIl4tEm2afwyw=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.32", "", { "os": "freebsd", "cpu": "x64" }, "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.31", "", { "os": "linux", "cpu": "arm" }, "sha512-LHmAaB3rB1GOJuHscKcL2Ts/LKLcb3YWTh2uQ/876rg/J9WE9kQ0kZ+3lRSYbth/YL8ln54j4JZmHpqQY3xptQ=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.32", "", { "os": "linux", "cpu": "arm" }, "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.31", "", { "os": "linux", "cpu": "arm64" }, "sha512-oTDZVfqIAjLB2I1yTiLyyhfPPO6dky33sTblxTCpe+ZT55WizN3KDoBKJ4yXG8shI6I4bRShVu29Xg0yAjyQYw=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.32", "", { "os": "linux", "cpu": "arm64" }, "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.31", "", { "os": "linux", "cpu": "arm64" }, "sha512-duJ3IkEBj9Xe9NYW1n8Y3483VXHGi8zQ0ZsLbK8464EJUXLF7CXM8Ry+jkkUw+ZvA+Zu1E/+C6p2Y6T9el0C9g=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.32", "", { "os": "linux", "cpu": "arm64" }, "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g=="], - "@rolldown/binding-linux-arm64-ohos": ["@rolldown/binding-linux-arm64-ohos@1.0.0-beta.31", "", { "os": "none", "cpu": "arm64" }, "sha512-qdbmU5QSZ0uoLZBYMxiHsMQmizqtzFGTVPU5oyU1n0jU0Mo+mkSzqZuL8VBnjHOHzhVxZsoAGH9JjiRzCnoGVA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.32", "", { "os": "linux", "cpu": "x64" }, "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.31", "", { "os": "linux", "cpu": "x64" }, "sha512-H7+r34TSV8udB2gAsebFM/YuEeNCkPGEAGJ1JE7SgI9XML6FflqcdKfrRSneQFsPaom/gCEc1g0WW5MZ0O3blw=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.32", "", { "os": "linux", "cpu": "x64" }, "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.31", "", { "os": "linux", "cpu": "x64" }, "sha512-zRm2YmzFVqbsmUsyyZnHfJrOlQUcWS/FJ5ZWL8Q1kZh5PnLBrTVZNpakIWwAxpN5gNEi9MmFd5YHocVJp8ps1Q=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.32", "", { "os": "none", "cpu": "arm64" }, "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.31", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.1" }, "cpu": "none" }, "sha512-fM1eUIuHLsNJXRlWOuIIex1oBJ89I0skFWo5r/D3KSJ5gD9MBd3g4Hp+v1JGohvyFE+7ylnwRxSUyMEeYpA69A=="], + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.32", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.3" }, "cpu": "none" }, "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.31", "", { "os": "win32", "cpu": "arm64" }, "sha512-4nftR9V2KHH3zjBwf6leuZZJQZ7v0d70ogjHIqB3SDsbDLvVEZiGSsSn2X6blSZRZeJSFzK0pp4kZ67zdZXwSw=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.32", "", { "os": "win32", "cpu": "arm64" }, "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew=="], - "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.31", "", { "os": "win32", "cpu": "ia32" }, "sha512-0TQcKu9xZVHYALit+WJsSuADGlTFfOXhnZoIHWWQhTk3OgbwwbYcSoZUXjRdFmR6Wswn4csHtJGN1oYKeQ6/2g=="], + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.32", "", { "os": "win32", "cpu": "ia32" }, "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.31", "", { "os": "win32", "cpu": "x64" }, "sha512-3zMICWwpZh1jrkkKDYIUCx/2wY3PXLICAS0AnbeLlhzfWPhCcpNK9eKhiTlLAZyTp+3kyipoi/ZSVIh+WDnBpQ=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.32", "", { "os": "win32", "cpu": "x64" }, "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.31", "", {}, "sha512-IaDZ9NhjOIOkYtm+hH0GX33h3iVZ2OeSUnFF0+7Z4+1GuKs4Kj5wK3+I2zNV9IPLfqV4XlwWif8SXrZNutxciQ=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.32", "", {}, "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.46.2", "", { "os": "android", "cpu": "arm" }, "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA=="], @@ -1008,7 +1008,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="], + "@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], @@ -2294,9 +2294,9 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], - "rolldown": ["rolldown@1.0.0-beta.31", "", { "dependencies": { "@oxc-project/runtime": "=0.80.0", "@oxc-project/types": "=0.80.0", "@rolldown/pluginutils": "1.0.0-beta.31", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.31", "@rolldown/binding-darwin-arm64": "1.0.0-beta.31", "@rolldown/binding-darwin-x64": "1.0.0-beta.31", "@rolldown/binding-freebsd-x64": "1.0.0-beta.31", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.31", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.31", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.31", "@rolldown/binding-linux-arm64-ohos": "1.0.0-beta.31", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.31", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.31", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.31", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.31", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.31", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.31" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-M2Q+RfG0FMJeSW3RSFTbvtjGVTcQpTQvN247D0EMSsPkpZFoinopR9oAnQiwgogQyzDuvKNnbyCbQQlmNAzSoQ=="], + "rolldown": ["rolldown@1.0.0-beta.32", "", { "dependencies": { "@oxc-project/runtime": "=0.81.0", "@oxc-project/types": "=0.81.0", "@rolldown/pluginutils": "1.0.0-beta.32", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.32", "@rolldown/binding-darwin-arm64": "1.0.0-beta.32", "@rolldown/binding-darwin-x64": "1.0.0-beta.32", "@rolldown/binding-freebsd-x64": "1.0.0-beta.32", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.32", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.32", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.32", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg=="], - "rolldown-plugin-dts": ["rolldown-plugin-dts@0.15.4", "", { "dependencies": { "@babel/generator": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/types": "^7.28.2", "ast-kit": "^2.1.1", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.1", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.0.3" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-6R+WLRJNfTNv60u7wLFS9vzINRs0jUMomiiRFSp8rgFgrudfQC9q3TB6oDv2jAgcsSyokZHCbHQIbSKI0Je/bA=="], + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.15.6", "", { "dependencies": { "@babel/generator": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/types": "^7.28.2", "ast-kit": "^2.1.1", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.1", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.0.3" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-AxQlyx3Nszob5QLmVUjz/VnC5BevtUo0h8tliuE0egddss7IbtCBU7GOe7biRU0fJNRQJmQjPKXFcc7K98j3+w=="], "rollup": ["rollup@4.46.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.46.2", "@rollup/rollup-android-arm64": "4.46.2", "@rollup/rollup-darwin-arm64": "4.46.2", "@rollup/rollup-darwin-x64": "4.46.2", "@rollup/rollup-freebsd-arm64": "4.46.2", "@rollup/rollup-freebsd-x64": "4.46.2", "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", "@rollup/rollup-linux-arm-musleabihf": "4.46.2", "@rollup/rollup-linux-arm64-gnu": "4.46.2", "@rollup/rollup-linux-arm64-musl": "4.46.2", "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", "@rollup/rollup-linux-ppc64-gnu": "4.46.2", "@rollup/rollup-linux-riscv64-gnu": "4.46.2", "@rollup/rollup-linux-riscv64-musl": "4.46.2", "@rollup/rollup-linux-s390x-gnu": "4.46.2", "@rollup/rollup-linux-x64-gnu": "4.46.2", "@rollup/rollup-linux-x64-musl": "4.46.2", "@rollup/rollup-win32-arm64-msvc": "4.46.2", "@rollup/rollup-win32-ia32-msvc": "4.46.2", "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg=="], @@ -2498,7 +2498,7 @@ "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - "tsdown": ["tsdown@0.13.3", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "^1.0.0-beta.31", "rolldown-plugin-dts": "^0.15.3", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "tree-kill": "^1.2.2", "unconfig": "^7.3.2" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-3ujweLJB70DdWXX3v7xnzrFhzW1F/6/99XhGeKzh1UCmZ+ttFmF7Czha7VaunA5Dq/u+z4aNz3n4GH748uivYg=="], + "tsdown": ["tsdown@0.14.1", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "latest", "rolldown-plugin-dts": "^0.15.6", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "tree-kill": "^1.2.2", "unconfig": "^7.3.2" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-/nBuFDKZeYln9hAxwWG5Cm55/823sNIVI693iVi0xRFHzf9OVUq4b/lx9PH1TErFr/IQ0kd2hutFbJIPM0XQWA=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -2664,7 +2664,7 @@ "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], - "zod": ["zod@4.0.15", "", {}, "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ=="], + "zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], @@ -3312,6 +3312,14 @@ "@ts-morph/common/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + "@types/fs-extra/@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="], + + "@types/jsonfile/@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="], + + "@types/node-fetch/@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="], + + "@types/papaparse/@types/node": ["@types/node@24.2.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], @@ -3322,6 +3330,8 @@ "cloudflare/@types/node": ["@types/node@18.19.121", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ=="], + "convex-helpers/zod": ["zod@4.0.15", "", {}, "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ=="], + "create-better-t-stack/typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], @@ -3358,6 +3368,8 @@ "fumadocs-mdx/esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="], + "fumadocs-mdx/zod": ["zod@4.0.15", "", {}, "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ=="], + "glob/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], "globby/@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], diff --git a/package.json b/package.json index 99636f9..0639bea 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "build": "turbo build", "dev": "turbo dev", "dev:cli": "turbo run dev --filter=create-better-t-stack", - "cli": "cd apps/cli && node dist/index.js", + "cli": "cd apps/cli && node dist/cli.js", "dev:web": "turbo run dev --filter=web", "build:web": "turbo run build --filter=web", "build:cli": "turbo run build --filter=create-better-t-stack",