mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): use biome js api to format all generated templates (#571)
This commit is contained in:
@@ -1,88 +1,90 @@
|
||||
{
|
||||
"name": "create-better-t-stack",
|
||||
"version": "2.43.1",
|
||||
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Aman Varshney",
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"templates",
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"better-t-stack",
|
||||
"typescript",
|
||||
"boilerplate",
|
||||
"starter",
|
||||
"cli",
|
||||
"turborepo",
|
||||
"trpc",
|
||||
"better-auth",
|
||||
"monorepo",
|
||||
"fullstack",
|
||||
"type-safety",
|
||||
"react",
|
||||
"react-native",
|
||||
"expo",
|
||||
"hono",
|
||||
"elysia",
|
||||
"drizzle",
|
||||
"prisma",
|
||||
"tanstack",
|
||||
"tailwind",
|
||||
"shadcn",
|
||||
"pwa",
|
||||
"tauri",
|
||||
"biome"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
|
||||
"directory": "apps/cli"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://better-t-stack.dev/",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check": "biome check --write .",
|
||||
"test": "bun run build && vitest run",
|
||||
"test:ui": "bun run build && vitest --ui",
|
||||
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.0.0-alpha.4",
|
||||
"consola": "^3.4.2",
|
||||
"execa": "^9.6.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"gradient-string": "^3.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"trpc-cli": "^0.10.2",
|
||||
"ts-morph": "^27.0.0",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^24.3.1",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
"name": "create-better-t-stack",
|
||||
"version": "2.43.1",
|
||||
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Aman Varshney",
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"templates",
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"better-t-stack",
|
||||
"typescript",
|
||||
"boilerplate",
|
||||
"starter",
|
||||
"cli",
|
||||
"turborepo",
|
||||
"trpc",
|
||||
"better-auth",
|
||||
"monorepo",
|
||||
"fullstack",
|
||||
"type-safety",
|
||||
"react",
|
||||
"react-native",
|
||||
"expo",
|
||||
"hono",
|
||||
"elysia",
|
||||
"drizzle",
|
||||
"prisma",
|
||||
"tanstack",
|
||||
"tailwind",
|
||||
"shadcn",
|
||||
"pwa",
|
||||
"tauri",
|
||||
"biome"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
|
||||
"directory": "apps/cli"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://better-t-stack.dev/",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check": "biome check --write .",
|
||||
"test": "bun run build && vitest run",
|
||||
"test:ui": "bun run build && vitest --ui",
|
||||
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biomejs/js-api": "^3.0.0",
|
||||
"@biomejs/wasm-nodejs": "^2.2.4",
|
||||
"@clack/prompts": "^1.0.0-alpha.4",
|
||||
"consola": "^3.4.2",
|
||||
"execa": "^9.6.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"gradient-string": "^3.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"trpc-cli": "^0.10.2",
|
||||
"ts-morph": "^27.0.0",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^24.3.1",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const dependencyVersionMap = {
|
||||
|
||||
"@elysiajs/cors": "^1.3.3",
|
||||
"@elysiajs/trpc": "^1.1.0",
|
||||
"elysia": "^1.3.21",
|
||||
elysia: "^1.3.21",
|
||||
|
||||
"@hono/node-server": "^1.14.4",
|
||||
"@hono/trpc-server": "^0.4.0",
|
||||
@@ -108,7 +108,7 @@ export const dependencyVersionMap = {
|
||||
|
||||
turbo: "^2.5.4",
|
||||
|
||||
"ai": "^5.0.39",
|
||||
ai: "^5.0.39",
|
||||
"@ai-sdk/google": "^2.0.13",
|
||||
"@ai-sdk/vue": "^2.0.39",
|
||||
"@ai-sdk/svelte": "^3.0.39",
|
||||
|
||||
@@ -47,11 +47,7 @@ export async function processAndCopyFiles(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (srcPath.endsWith(".hbs")) {
|
||||
await processTemplate(srcPath, destPath, context);
|
||||
} else {
|
||||
await fs.copy(srcPath, destPath, { overwrite: true });
|
||||
}
|
||||
await processTemplate(srcPath, destPath, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,12 @@ export async function setupNextAlchemyDeploy(
|
||||
|
||||
await addPackageDependency({
|
||||
dependencies: ["@opennextjs/cloudflare"],
|
||||
devDependencies: ["alchemy", "dotenv", "wrangler", "@cloudflare/workers-types"],
|
||||
devDependencies: [
|
||||
"alchemy",
|
||||
"dotenv",
|
||||
"wrangler",
|
||||
"@cloudflare/workers-types",
|
||||
],
|
||||
projectDir: webAppDir,
|
||||
});
|
||||
|
||||
|
||||
95
apps/cli/src/utils/biome-formatter.ts
Normal file
95
apps/cli/src/utils/biome-formatter.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import path from "node:path";
|
||||
import { Biome } from "@biomejs/js-api/nodejs";
|
||||
import consola from "consola";
|
||||
|
||||
let biome: Biome | null = null;
|
||||
let projectKey: number | null = null;
|
||||
|
||||
async function initializeBiome(): Promise<{
|
||||
biome: Biome;
|
||||
projectKey: number;
|
||||
}> {
|
||||
if (biome && projectKey !== null) return { biome, projectKey };
|
||||
|
||||
try {
|
||||
biome = new Biome();
|
||||
const result = biome.openProject("./");
|
||||
projectKey = result.projectKey;
|
||||
|
||||
biome.applyConfiguration(projectKey, {
|
||||
formatter: {
|
||||
enabled: true,
|
||||
indentStyle: "tab",
|
||||
indentWidth: 2,
|
||||
lineWidth: 80,
|
||||
},
|
||||
linter: {
|
||||
enabled: false,
|
||||
},
|
||||
javascript: {
|
||||
formatter: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
json: {
|
||||
formatter: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return { biome, projectKey };
|
||||
} catch (error) {
|
||||
consola.error("Failed to initialize Biome:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function isSupportedFile(filePath: string): boolean {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const supportedExtensions = [".js", ".jsx", ".ts", ".tsx", ".json", ".jsonc"];
|
||||
return supportedExtensions.includes(ext);
|
||||
}
|
||||
|
||||
function shouldSkipFile(filePath: string): boolean {
|
||||
const basename = path.basename(filePath);
|
||||
const skipPatterns = [
|
||||
".hbs",
|
||||
"package-lock.json",
|
||||
"yarn.lock",
|
||||
"pnpm-lock.yaml",
|
||||
"bun.lock",
|
||||
".d.ts",
|
||||
];
|
||||
|
||||
return skipPatterns.some((pattern) => basename.includes(pattern));
|
||||
}
|
||||
|
||||
export async function formatFileWithBiome(
|
||||
filePath: string,
|
||||
content: string,
|
||||
): Promise<string | null> {
|
||||
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const { biome: biomeInstance, projectKey: key } = await initializeBiome();
|
||||
|
||||
const result = biomeInstance.formatContent(key, content, {
|
||||
filePath: path.basename(filePath),
|
||||
});
|
||||
|
||||
if (result.diagnostics && result.diagnostics.length > 0) {
|
||||
consola.debug(
|
||||
`Biome formatting diagnostics for ${filePath}:`,
|
||||
result.diagnostics,
|
||||
);
|
||||
}
|
||||
|
||||
return result.content;
|
||||
} catch (error) {
|
||||
consola.warn(`Failed to format ${filePath} with Biome:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,25 +3,36 @@ import consola from "consola";
|
||||
import fs from "fs-extra";
|
||||
import handlebars from "handlebars";
|
||||
import type { ProjectConfig } from "../types";
|
||||
import { formatFileWithBiome } from "./biome-formatter";
|
||||
|
||||
/**
|
||||
* Processes a Handlebars template file and writes the output to the destination.
|
||||
* @param srcPath Path to the source .hbs template file.
|
||||
* @param destPath Path to write the processed file.
|
||||
* @param context Data to be passed to the Handlebars template.
|
||||
*/
|
||||
export async function processTemplate(
|
||||
srcPath: string,
|
||||
destPath: string,
|
||||
context: ProjectConfig,
|
||||
) {
|
||||
try {
|
||||
const templateContent = await fs.readFile(srcPath, "utf-8");
|
||||
const template = handlebars.compile(templateContent);
|
||||
const processedContent = template(context);
|
||||
|
||||
await fs.ensureDir(path.dirname(destPath));
|
||||
await fs.writeFile(destPath, processedContent);
|
||||
|
||||
let content: string;
|
||||
|
||||
if (srcPath.endsWith(".hbs")) {
|
||||
const templateContent = await fs.readFile(srcPath, "utf-8");
|
||||
const template = handlebars.compile(templateContent);
|
||||
content = template(context);
|
||||
} else {
|
||||
content = await fs.readFile(srcPath, "utf-8");
|
||||
}
|
||||
|
||||
try {
|
||||
const formattedContent = await formatFileWithBiome(destPath, content);
|
||||
if (formattedContent) {
|
||||
content = formattedContent;
|
||||
}
|
||||
} catch (formatError) {
|
||||
consola.debug(`Failed to format ${destPath}:`, formatError);
|
||||
}
|
||||
|
||||
await fs.writeFile(destPath, content);
|
||||
} catch (error) {
|
||||
consola.error(`Error processing template ${srcPath}:`, error);
|
||||
throw new Error(`Failed to process template ${srcPath}`);
|
||||
|
||||
Reference in New Issue
Block a user