mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add programmatic api (#494)
This commit is contained in:
5
.changeset/eleven-walls-tease.md
Normal file
5
.changeset/eleven-walls-tease.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Add programmatic API
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
3
apps/cli/src/cli.ts
Normal file
3
apps/cli/src/cli.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createBtsCli } from "./index";
|
||||
|
||||
createBtsCli().run();
|
||||
@@ -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<InitResult> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -108,9 +108,16 @@ export const WebDeploySchema = z
|
||||
.describe("Web deployment");
|
||||
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 = {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<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(
|
||||
options: CLIInput,
|
||||
providedFlags: Set<string>,
|
||||
@@ -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<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> {
|
||||
return new Set(
|
||||
Object.keys(options).filter(
|
||||
|
||||
@@ -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)
|
||||
|
||||
340
apps/cli/test/programmatic-api.test.ts
Normal file
340
apps/cli/test/programmatic-api.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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 <pm>`: `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 <setup>`: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `mongodb-atlas`, `docker`
|
||||
- `--examples <types...>`: `none`, `todo`, `ai`
|
||||
- `--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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"title": "CLI",
|
||||
"defaultOpen": true,
|
||||
"pages": ["index", "options", "compatibility"]
|
||||
"pages": ["index", "programmatic-api", "options", "compatibility"]
|
||||
}
|
||||
|
||||
@@ -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 <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 <type>`
|
||||
|
||||
276
apps/web/content/docs/cli/programmatic-api.mdx
Normal file
276
apps/web/content/docs/cli/programmatic-api.mdx
Normal 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
|
||||
});
|
||||
```
|
||||
66
bun.lock
66
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=="],
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user