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

@@ -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<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,
};