feat(cli): add programmatic api (#494)

This commit is contained in:
Aman Varshney
2025-08-12 07:40:19 +05:30
committed by GitHub
parent 5b2827ef12
commit aecde5a54e
18 changed files with 1295 additions and 203 deletions

View File

@@ -0,0 +1,5 @@
---
"create-better-t-stack": minor
---
Add programmatic API

View File

@@ -6,7 +6,7 @@
"license": "MIT", "license": "MIT",
"author": "Aman Varshney", "author": "Aman Varshney",
"bin": { "bin": {
"create-better-t-stack": "dist/index.js" "create-better-t-stack": "dist/cli.js"
}, },
"files": [ "files": [
"templates", "templates",
@@ -53,6 +53,12 @@
"test:with-build": "bun run build && WITH_BUILD=1 vitest run", "test:with-build": "bun run build && WITH_BUILD=1 vitest run",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"dependencies": { "dependencies": {
"@clack/prompts": "^0.11.0", "@clack/prompts": "^0.11.0",
"consola": "^3.4.2", "consola": "^3.4.2",
@@ -65,12 +71,12 @@
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"trpc-cli": "^0.10.2", "trpc-cli": "^0.10.2",
"ts-morph": "^26.0.0", "ts-morph": "^26.0.0",
"zod": "^4.0.15" "zod": "^4.0.17"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.2.0", "@types/node": "^24.2.1",
"tsdown": "^0.13.3", "tsdown": "^0.14.1",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vitest": "^3.2.4" "vitest": "^3.2.4"
} }

3
apps/cli/src/cli.ts Normal file
View File

@@ -0,0 +1,3 @@
import { createBtsCli } from "./index";
createBtsCli().run();

View File

@@ -1,5 +1,6 @@
import path from "node:path"; import path from "node:path";
import { intro, log, outro } from "@clack/prompts"; import { intro, log, outro } from "@clack/prompts";
import consola from "consola";
import fs from "fs-extra"; import fs from "fs-extra";
import pc from "picocolors"; import pc from "picocolors";
import { DEFAULT_CONFIG } from "../../constants"; import { DEFAULT_CONFIG } from "../../constants";
@@ -7,7 +8,13 @@ import { getAddonsToAdd } from "../../prompts/addons";
import { gatherConfig } from "../../prompts/config-prompts"; import { gatherConfig } from "../../prompts/config-prompts";
import { getProjectName } from "../../prompts/project-name"; import { getProjectName } from "../../prompts/project-name";
import { getDeploymentToAdd } from "../../prompts/web-deploy"; 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 { trackProjectCreation } from "../../utils/analytics";
import { displayConfig } from "../../utils/display-config"; import { displayConfig } from "../../utils/display-config";
import { exitWithError, handleError } from "../../utils/errors"; import { exitWithError, handleError } from "../../utils/errors";
@@ -26,113 +33,221 @@ import { installDependencies } from "./install-dependencies";
export async function createProjectHandler( export async function createProjectHandler(
input: CreateInput & { projectName?: string }, input: CreateInput & { projectName?: string },
) { ): Promise<InitResult> {
const startTime = Date.now(); 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 { try {
renderTitle(); if (input.directoryConflict) {
intro(pc.magenta("Creating a new Better-T Stack project")); const result = await handleDirectoryConflictProgrammatically(
currentPathInput,
let currentPathInput: string; input.directoryConflict,
if (input.yes && input.projectName) { );
currentPathInput = input.projectName; finalPathInput = result.finalPathInput;
} else if (input.yes) { shouldClearDirectory = result.shouldClearDirectory;
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 { } 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 } = const { finalResolvedPath, finalBaseName } = await setupProjectDirectory(
await handleDirectoryConflict(currentPathInput); finalPathInput,
shouldClearDirectory,
);
const { finalResolvedPath, finalBaseName } = await setupProjectDirectory( const cliInput = {
finalPathInput, ...input,
shouldClearDirectory, projectDirectory: input.projectName,
); };
const cliInput = { const providedFlags = getProvidedFlags(cliInput);
...input,
projectDirectory: input.projectName, 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); if (config.backend === "convex") {
const flagConfig = processAndValidateFlags( log.info(
cliInput, "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",
providedFlags, );
finalBaseName, } else if (config.backend === "none") {
); log.info(
const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig; "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",
);
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; log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
if (input.yes) { log.message(displayConfig(config));
config = { log.message("");
...DEFAULT_CONFIG, } else {
...flagConfig, config = await gatherConfig(
projectName: finalBaseName, flagConfig,
projectDir: finalResolvedPath, finalBaseName,
relativePath: finalPathInput, finalResolvedPath,
}; finalPathInput,
);
}
if (config.backend === "convex") { await createProject(config);
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", const reproducibleCommand = generateReproducibleCommand(config);
); log.success(
} else if (config.backend === "none") { pc.blue(
log.info( `You can reproduce this setup with the following command:\n${reproducibleCommand}`,
"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 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( return { finalPathInput, shouldClearDirectory: false };
pc.yellow("Using default/flag options (config prompts skipped):"),
);
log.message(displayConfig(config));
log.message("");
} else {
config = await gatherConfig(
flagConfig,
finalBaseName,
finalResolvedPath,
finalPathInput,
);
} }
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); default:
log.success( throw new Error(`Unknown directory conflict strategy: ${strategy}`);
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");
} }
} }

View File

@@ -7,17 +7,35 @@ import {
createProjectHandler, createProjectHandler,
} from "./helpers/project-generation/command-handlers"; } from "./helpers/project-generation/command-handlers";
import { import {
type AddInput,
type Addons,
AddonsSchema, AddonsSchema,
type API,
APISchema, APISchema,
type Backend,
BackendSchema, BackendSchema,
type BetterTStackConfig,
type CreateInput,
type Database,
DatabaseSchema, DatabaseSchema,
type DatabaseSetup,
DatabaseSetupSchema, DatabaseSetupSchema,
type DirectoryConflict,
DirectoryConflictSchema,
type Examples,
ExamplesSchema, ExamplesSchema,
type Frontend,
FrontendSchema, FrontendSchema,
type InitResult,
type ORM,
ORMSchema, ORMSchema,
type PackageManager,
PackageManagerSchema, PackageManagerSchema,
type ProjectConfig,
ProjectNameSchema, ProjectNameSchema,
type Runtime,
RuntimeSchema, RuntimeSchema,
type WebDeploy,
WebDeploySchema, WebDeploySchema,
} from "./types"; } from "./types";
import { handleError } from "./utils/errors"; import { handleError } from "./utils/errors";
@@ -28,7 +46,7 @@ import { displaySponsors, fetchSponsors } from "./utils/sponsors";
const t = trpcServer.initTRPC.create(); const t = trpcServer.initTRPC.create();
const router = t.router({ export const router = t.router({
init: t.procedure init: t.procedure
.meta({ .meta({
description: "Create a new Better-T Stack project", description: "Create a new Better-T Stack project",
@@ -44,6 +62,18 @@ const router = t.router({
.optional() .optional()
.default(false) .default(false)
.describe("Use default configuration"), .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(), database: DatabaseSchema.optional(),
orm: ORMSchema.optional(), orm: ORMSchema.optional(),
auth: z.boolean().optional(), auth: z.boolean().optional(),
@@ -58,6 +88,8 @@ const router = t.router({
runtime: RuntimeSchema.optional(), runtime: RuntimeSchema.optional(),
api: APISchema.optional(), api: APISchema.optional(),
webDeploy: WebDeploySchema.optional(), webDeploy: WebDeploySchema.optional(),
directoryConflict: DirectoryConflictSchema.optional(),
renderTitle: z.boolean().optional(),
}), }),
]), ]),
) )
@@ -67,7 +99,11 @@ const router = t.router({
projectName, projectName,
...options, ...options,
}; };
await createProjectHandler(combinedInput); const result = await createProjectHandler(combinedInput);
if (options.verbose) {
return result;
}
}), }),
add: t.procedure add: t.procedure
.meta({ .meta({
@@ -129,8 +165,90 @@ const router = t.router({
}), }),
}); });
createCli({ const caller = t.createCallerFactory(router)({});
router,
name: "create-better-t-stack", export function createBtsCli() {
version: getLatestCLIVersion(), return createCli({
}).run(); 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<InitResult> {
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,
};

View File

@@ -108,9 +108,16 @@ export const WebDeploySchema = z
.describe("Web deployment"); .describe("Web deployment");
export type WebDeploy = z.infer<typeof WebDeploySchema>; export type WebDeploy = z.infer<typeof WebDeploySchema>;
export const DirectoryConflictSchema = z
.enum(["merge", "overwrite", "increment", "error"])
.describe("How to handle existing directory conflicts");
export type DirectoryConflict = z.infer<typeof DirectoryConflictSchema>;
export type CreateInput = { export type CreateInput = {
projectName?: string; projectName?: string;
yes?: boolean; yes?: boolean;
yolo?: boolean;
verbose?: boolean;
database?: Database; database?: Database;
orm?: ORM; orm?: ORM;
auth?: boolean; auth?: boolean;
@@ -125,6 +132,8 @@ export type CreateInput = {
runtime?: Runtime; runtime?: Runtime;
api?: API; api?: API;
webDeploy?: WebDeploy; webDeploy?: WebDeploy;
directoryConflict?: DirectoryConflict;
renderTitle?: boolean;
}; };
export type AddInput = { export type AddInput = {
@@ -175,3 +184,14 @@ export interface BetterTStackConfig {
api: API; api: API;
webDeploy: WebDeploy; webDeploy: WebDeploy;
} }
export interface InitResult {
success: boolean;
projectConfig: ProjectConfig;
reproducibleCommand: string;
timeScaffolded: string;
elapsedTimeMs: number;
projectDirectory: string;
relativePath: string;
error?: string;
}

View File

@@ -2,13 +2,23 @@ import { cancel } from "@clack/prompts";
import consola from "consola"; import consola from "consola";
import pc from "picocolors"; import pc from "picocolors";
function isProgrammatic(): boolean {
return process.env.BTS_PROGRAMMATIC === "1";
}
export function exitWithError(message: string): never { export function exitWithError(message: string): never {
consola.error(pc.red(message)); consola.error(pc.red(message));
if (isProgrammatic()) {
throw new Error(message);
}
process.exit(1); process.exit(1);
} }
export function exitCancelled(message = "Operation cancelled"): never { export function exitCancelled(message = "Operation cancelled"): never {
cancel(pc.red(message)); cancel(pc.red(message));
if (isProgrammatic()) {
throw new Error(message);
}
process.exit(0); process.exit(0);
} }
@@ -16,5 +26,8 @@ export function handleError(error: unknown, fallbackMessage?: string): never {
const message = const message =
error instanceof Error ? error.message : fallbackMessage || String(error); error instanceof Error ? error.message : fallbackMessage || String(error);
consola.error(pc.red(message)); consola.error(pc.red(message));
if (isProgrammatic()) {
throw new Error(message);
}
process.exit(1); process.exit(1);
} }

View File

@@ -7,6 +7,7 @@ import { exitCancelled, handleError } from "./errors";
export async function handleDirectoryConflict( export async function handleDirectoryConflict(
currentPathInput: string, currentPathInput: string,
silent = false,
): Promise<{ ): Promise<{
finalPathInput: string; finalPathInput: string;
shouldClearDirectory: boolean; shouldClearDirectory: boolean;
@@ -20,6 +21,12 @@ export async function handleDirectoryConflict(
return { finalPathInput: currentPathInput, shouldClearDirectory: false }; 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( log.warn(
`Directory "${pc.yellow( `Directory "${pc.yellow(
currentPathInput, currentPathInput,

View File

@@ -1,13 +1,10 @@
import path from "node:path"; import path from "node:path";
import { import {
type Addons,
type API, type API,
type Backend, type Backend,
type CLIInput, type CLIInput,
type Database, type Database,
type DatabaseSetup, type DatabaseSetup,
type Examples,
type Frontend,
type ORM, type ORM,
type PackageManager, type PackageManager,
type ProjectConfig, type ProjectConfig,
@@ -28,6 +25,36 @@ import {
} from "./utils/compatibility-rules"; } from "./utils/compatibility-rules";
import { exitWithError } from "./utils/errors"; import { exitWithError } from "./utils/errors";
function processArrayOption<T>(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( export function processAndValidateFlags(
options: CLIInput, options: CLIInput,
providedFlags: Set<string>, providedFlags: Set<string>,
@@ -96,29 +123,13 @@ export function processAndValidateFlags(
config.webDeploy = options.webDeploy as WebDeploy; config.webDeploy = options.webDeploy as WebDeploy;
} }
if (projectName) { const derivedName = deriveProjectName(projectName, options.projectDirectory);
const result = ProjectNameSchema.safeParse(path.basename(projectName)); if (derivedName) {
if (!result.success) { const nameToValidate = projectName
exitWithError( ? path.basename(projectName)
`Invalid project name: ${ : derivedName;
result.error.issues[0]?.message || "Invalid project name" validateProjectName(nameToValidate);
}`, config.projectName = projectName || derivedName;
);
}
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;
} }
if (options.frontend && options.frontend.length > 0) { if (options.frontend && options.frontend.length > 0) {
@@ -128,9 +139,7 @@ export function processAndValidateFlags(
} }
config.frontend = []; config.frontend = [];
} else { } else {
const validOptions = options.frontend.filter( const validOptions = processArrayOption(options.frontend);
(f): f is Frontend => f !== "none",
);
ensureSingleWebAndNative(validOptions); ensureSingleWebAndNative(validOptions);
config.frontend = validOptions; config.frontend = validOptions;
} }
@@ -152,9 +161,7 @@ export function processAndValidateFlags(
} }
config.addons = []; config.addons = [];
} else { } else {
config.addons = options.addons.filter( config.addons = processArrayOption(options.addons);
(addon): addon is Addons => addon !== "none",
);
} }
} }
if (options.examples && options.examples.length > 0) { if (options.examples && options.examples.length > 0) {
@@ -164,9 +171,7 @@ export function processAndValidateFlags(
} }
config.examples = []; config.examples = [];
} else { } else {
config.examples = options.examples.filter( config.examples = processArrayOption(options.examples);
(ex): ex is Examples => ex !== "none",
);
if (options.examples.includes("none") && config.backend !== "convex") { if (options.examples.includes("none") && config.backend !== "convex") {
config.examples = []; config.examples = [];
} }
@@ -421,6 +426,85 @@ export function validateConfigCompatibility(config: Partial<ProjectConfig>) {
); );
} }
export function processProvidedFlagsWithoutValidation(
options: CLIInput,
projectName?: string,
): Partial<ProjectConfig> {
const config: Partial<ProjectConfig> = {};
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<string> { export function getProvidedFlags(options: CLIInput): Set<string> {
return new Set( return new Set(
Object.keys(options).filter( Object.keys(options).filter(

View File

@@ -9,9 +9,33 @@ import {
removeSync, removeSync,
} from "fs-extra"; } from "fs-extra";
import * as JSONC from "jsonc-parser"; import * as JSONC from "jsonc-parser";
import { FailedToExitError } from "trpc-cli";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; 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) { function createTmpDir(_prefix: string) {
const dir = join(__dirname, "..", ".smoke"); const dir = join(__dirname, "..", ".smoke");
@@ -22,39 +46,30 @@ function createTmpDir(_prefix: string) {
return dir; return dir;
} }
async function runCli(args: string[], cwd: string, env?: NodeJS.ProcessEnv) { async function runCliExpectingError(args: string[], cwd: string) {
const subprocess = execa("node", [CLI_BIN, ...args], { const previous = process.cwd();
cwd, process.chdir(cwd);
env: { try {
...process.env, const cli = createBtsCli();
BTS_TELEMETRY_DISABLED: "1", let threw = false;
...env, await cli
}, .run({
}); argv: args,
subprocess.stdout?.pipe(process.stdout); logger: { info: () => {}, error: () => {} },
subprocess.stderr?.pipe(process.stderr); process: { exit: () => void 0 as never },
const { exitCode } = await subprocess; })
expect(exitCode).toBe(0); .catch((err) => {
} threw = true;
let e: unknown = err;
async function runCliExpectingError( while (e instanceof FailedToExitError) {
args: string[], if (e.exitCode === 0) throw new Error("Expected failure");
cwd: string, e = e.cause;
env?: NodeJS.ProcessEnv, }
) { });
const subprocess = execa("node", [CLI_BIN, ...args], { expect(threw).toBe(true);
cwd, } finally {
env: { process.chdir(previous);
...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);
} }
function assertScaffoldedProject(dir: string) { function assertScaffoldedProject(dir: string) {
@@ -310,9 +325,8 @@ describe("create-better-t-stack smoke", () => {
expect(exitCode).toBe(0); expect(exitCode).toBe(0);
consola.success("CLI build completed"); consola.success("CLI build completed");
const cliBinExists = existsSync(CLI_BIN); process.env.BTS_TELEMETRY_DISABLED = "1";
expect(cliBinExists).toBe(true); consola.info("Programmatic CLI mode");
consola.info(`CLI binary: ${CLI_BIN}`);
}); });
// Exhaustive matrix: all frontends x standard backends (no db, no orm, no api, no auth) // Exhaustive matrix: all frontends x standard backends (no db, no orm, no api, no auth)

View File

@@ -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<name>", {
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);
});
});

View File

@@ -1,11 +1,12 @@
import { defineConfig } from "tsdown"; import { defineConfig } from "tsdown";
export default defineConfig({ export default defineConfig({
entry: ["src/index.ts"], entry: ["src/index.ts", "src/cli.ts"],
format: ["esm"], format: ["esm"],
clean: true, clean: true,
shims: true, shims: true,
outDir: "dist", outDir: "dist",
dts: true,
outputOptions: { outputOptions: {
banner: "#!/usr/bin/env node", banner: "#!/usr/bin/env node",
}, },

View File

@@ -22,6 +22,8 @@ create-better-t-stack [project-directory] [options]
### Key Options ### Key Options
- `--yes, -y`: Use default configuration (skips prompts) - `--yes, -y`: Use default configuration (skips prompts)
- `--verbose`: Show detailed result information as JSON
- `--yolo`: Bypass validations and compatibility checks
- `--package-manager <pm>`: `npm`, `pnpm`, `bun` - `--package-manager <pm>`: `npm`, `pnpm`, `bun`
- `--install / --no-install`: Install dependencies after creation - `--install / --no-install`: Install dependencies after creation
- `--git / --no-git`: Initialize Git repository - `--git / --no-git`: Initialize Git repository
@@ -34,6 +36,8 @@ create-better-t-stack [project-directory] [options]
- `--db-setup <setup>`: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `mongodb-atlas`, `docker` - `--db-setup <setup>`: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `mongodb-atlas`, `docker`
- `--examples <types...>`: `none`, `todo`, `ai` - `--examples <types...>`: `none`, `todo`, `ai`
- `--web-deploy <setup>`: `none`, `workers` - `--web-deploy <setup>`: `none`, `workers`
- `--directory-conflict <strategy>`: `merge`, `overwrite`, `increment`, `error`
- `--render-title / --no-render-title`: Show/hide ASCII art title
See the full reference in [Options](/docs/cli/options). See the full reference in [Options](/docs/cli/options).
@@ -147,3 +151,35 @@ create-better-t-stack api-server \
cd my-existing-project cd my-existing-project
create-better-t-stack add --addons tauri starlight --install 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.

View File

@@ -1,5 +1,5 @@
{ {
"title": "CLI", "title": "CLI",
"defaultOpen": true, "defaultOpen": true,
"pages": ["index", "options", "compatibility"] "pages": ["index", "programmatic-api", "options", "compatibility"]
} }

View File

@@ -37,6 +37,48 @@ Control Git repository initialization.
create-better-t-stack --no-git 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 <strategy>`
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 Options
### `--database <type>` ### `--database <type>`

View File

@@ -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<InitResult>`
**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<void>`
### `docs()`
Open documentation in browser (same as CLI).
**Returns:** `Promise<void>`
### `builder()`
Open web-based stack builder (same as CLI).
**Returns:** `Promise<void>`
## 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
});
```

View File

@@ -16,7 +16,7 @@
"name": "create-better-t-stack", "name": "create-better-t-stack",
"version": "2.31.0", "version": "2.31.0",
"bin": { "bin": {
"create-better-t-stack": "dist/index.js", "create-better-t-stack": "dist/cli.js",
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "^0.11.0", "@clack/prompts": "^0.11.0",
@@ -30,12 +30,12 @@
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"trpc-cli": "^0.10.2", "trpc-cli": "^0.10.2",
"ts-morph": "^26.0.0", "ts-morph": "^26.0.0",
"zod": "^4.0.15", "zod": "^4.0.17",
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.2.0", "@types/node": "^24.2.1",
"tsdown": "^0.13.3", "tsdown": "^0.14.1",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vitest": "^3.2.4", "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=="], "@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=="], "@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=="], "@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=="], "@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=="], "@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=="], "@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/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=="], "@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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "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=="], "@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=="], "@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=="], "@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=="], "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=="], "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=="], "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/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=="], "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=="], "globby/@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="],

View File

@@ -5,7 +5,7 @@
"build": "turbo build", "build": "turbo build",
"dev": "turbo dev", "dev": "turbo dev",
"dev:cli": "turbo run dev --filter=create-better-t-stack", "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", "dev:web": "turbo run dev --filter=web",
"build:web": "turbo run build --filter=web", "build:web": "turbo run build --filter=web",
"build:cli": "turbo run build --filter=create-better-t-stack", "build:cli": "turbo run build --filter=create-better-t-stack",