Skip to content

Commit ce97790

Browse files
feat(cli): use biome js api to format all generated templates (#571)
1 parent 5e91f79 commit ce97790

File tree

9 files changed

+293
-121
lines changed

9 files changed

+293
-121
lines changed

apps/cli/package.json

Lines changed: 88 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,90 @@
11
{
2-
"name": "create-better-t-stack",
3-
"version": "2.43.1",
4-
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5-
"type": "module",
6-
"license": "MIT",
7-
"author": "Aman Varshney",
8-
"bin": {
9-
"create-better-t-stack": "dist/cli.js"
10-
},
11-
"files": [
12-
"templates",
13-
"dist"
14-
],
15-
"keywords": [
16-
"better-t-stack",
17-
"typescript",
18-
"boilerplate",
19-
"starter",
20-
"cli",
21-
"turborepo",
22-
"trpc",
23-
"better-auth",
24-
"monorepo",
25-
"fullstack",
26-
"type-safety",
27-
"react",
28-
"react-native",
29-
"expo",
30-
"hono",
31-
"elysia",
32-
"drizzle",
33-
"prisma",
34-
"tanstack",
35-
"tailwind",
36-
"shadcn",
37-
"pwa",
38-
"tauri",
39-
"biome"
40-
],
41-
"repository": {
42-
"type": "git",
43-
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
44-
"directory": "apps/cli"
45-
},
46-
"publishConfig": {
47-
"access": "public"
48-
},
49-
"homepage": "https://better-t-stack.dev/",
50-
"scripts": {
51-
"build": "tsdown",
52-
"dev": "tsdown --watch",
53-
"check-types": "tsc --noEmit",
54-
"check": "biome check --write .",
55-
"test": "bun run build && vitest run",
56-
"test:ui": "bun run build && vitest --ui",
57-
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
58-
"prepublishOnly": "npm run build"
59-
},
60-
"exports": {
61-
".": {
62-
"types": "./dist/index.d.ts",
63-
"import": "./dist/index.js"
64-
}
65-
},
66-
"dependencies": {
67-
"@clack/prompts": "^1.0.0-alpha.4",
68-
"consola": "^3.4.2",
69-
"execa": "^9.6.0",
70-
"fs-extra": "^11.3.1",
71-
"gradient-string": "^3.0.0",
72-
"handlebars": "^4.7.8",
73-
"jsonc-parser": "^3.3.1",
74-
"picocolors": "^1.1.1",
75-
"tinyglobby": "^0.2.15",
76-
"trpc-cli": "^0.10.2",
77-
"ts-morph": "^27.0.0",
78-
"zod": "^4.1.5"
79-
},
80-
"devDependencies": {
81-
"@types/fs-extra": "^11.0.4",
82-
"@types/node": "^24.3.1",
83-
"@vitest/ui": "^3.2.4",
84-
"tsdown": "^0.14.2",
85-
"typescript": "^5.9.2",
86-
"vitest": "^3.2.4"
87-
}
2+
"name": "create-better-t-stack",
3+
"version": "2.43.1",
4+
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5+
"type": "module",
6+
"license": "MIT",
7+
"author": "Aman Varshney",
8+
"bin": {
9+
"create-better-t-stack": "dist/cli.js"
10+
},
11+
"files": [
12+
"templates",
13+
"dist"
14+
],
15+
"keywords": [
16+
"better-t-stack",
17+
"typescript",
18+
"boilerplate",
19+
"starter",
20+
"cli",
21+
"turborepo",
22+
"trpc",
23+
"better-auth",
24+
"monorepo",
25+
"fullstack",
26+
"type-safety",
27+
"react",
28+
"react-native",
29+
"expo",
30+
"hono",
31+
"elysia",
32+
"drizzle",
33+
"prisma",
34+
"tanstack",
35+
"tailwind",
36+
"shadcn",
37+
"pwa",
38+
"tauri",
39+
"biome"
40+
],
41+
"repository": {
42+
"type": "git",
43+
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
44+
"directory": "apps/cli"
45+
},
46+
"publishConfig": {
47+
"access": "public"
48+
},
49+
"homepage": "https://better-t-stack.dev/",
50+
"scripts": {
51+
"build": "tsdown",
52+
"dev": "tsdown --watch",
53+
"check-types": "tsc --noEmit",
54+
"check": "biome check --write .",
55+
"test": "bun run build && vitest run",
56+
"test:ui": "bun run build && vitest --ui",
57+
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
58+
"prepublishOnly": "npm run build"
59+
},
60+
"exports": {
61+
".": {
62+
"types": "./dist/index.d.ts",
63+
"import": "./dist/index.js"
64+
}
65+
},
66+
"dependencies": {
67+
"@biomejs/js-api": "^3.0.0",
68+
"@biomejs/wasm-nodejs": "^2.2.4",
69+
"@clack/prompts": "^1.0.0-alpha.4",
70+
"consola": "^3.4.2",
71+
"execa": "^9.6.0",
72+
"fs-extra": "^11.3.1",
73+
"gradient-string": "^3.0.0",
74+
"handlebars": "^4.7.8",
75+
"jsonc-parser": "^3.3.1",
76+
"picocolors": "^1.1.1",
77+
"tinyglobby": "^0.2.15",
78+
"trpc-cli": "^0.10.2",
79+
"ts-morph": "^27.0.0",
80+
"zod": "^4.1.5"
81+
},
82+
"devDependencies": {
83+
"@types/fs-extra": "^11.0.4",
84+
"@types/node": "^24.3.1",
85+
"@vitest/ui": "^3.2.4",
86+
"tsdown": "^0.14.2",
87+
"typescript": "^5.9.2",
88+
"vitest": "^3.2.4"
89+
}
8890
}

apps/cli/src/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const dependencyVersionMap = {
9292

9393
"@elysiajs/cors": "^1.3.3",
9494
"@elysiajs/trpc": "^1.1.0",
95-
"elysia": "^1.3.21",
95+
elysia: "^1.3.21",
9696

9797
"@hono/node-server": "^1.14.4",
9898
"@hono/trpc-server": "^0.4.0",
@@ -108,7 +108,7 @@ export const dependencyVersionMap = {
108108

109109
turbo: "^2.5.4",
110110

111-
"ai": "^5.0.39",
111+
ai: "^5.0.39",
112112
"@ai-sdk/google": "^2.0.13",
113113
"@ai-sdk/vue": "^2.0.39",
114114
"@ai-sdk/svelte": "^3.0.39",

apps/cli/src/helpers/core/template-manager.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,7 @@ export async function processAndCopyFiles(
4747
continue;
4848
}
4949

50-
if (srcPath.endsWith(".hbs")) {
51-
await processTemplate(srcPath, destPath, context);
52-
} else {
53-
await fs.copy(srcPath, destPath, { overwrite: true });
54-
}
50+
await processTemplate(srcPath, destPath, context);
5551
}
5652
}
5753

apps/cli/src/helpers/deployment/alchemy/alchemy-next-setup.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ export async function setupNextAlchemyDeploy(
1313

1414
await addPackageDependency({
1515
dependencies: ["@opennextjs/cloudflare"],
16-
devDependencies: ["alchemy", "dotenv", "wrangler", "@cloudflare/workers-types"],
16+
devDependencies: [
17+
"alchemy",
18+
"dotenv",
19+
"wrangler",
20+
"@cloudflare/workers-types",
21+
],
1722
projectDir: webAppDir,
1823
});
1924

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import path from "node:path";
2+
import { Biome } from "@biomejs/js-api/nodejs";
3+
import consola from "consola";
4+
5+
let biome: Biome | null = null;
6+
let projectKey: number | null = null;
7+
8+
async function initializeBiome(): Promise<{
9+
biome: Biome;
10+
projectKey: number;
11+
}> {
12+
if (biome && projectKey !== null) return { biome, projectKey };
13+
14+
try {
15+
biome = new Biome();
16+
const result = biome.openProject("./");
17+
projectKey = result.projectKey;
18+
19+
biome.applyConfiguration(projectKey, {
20+
formatter: {
21+
enabled: true,
22+
indentStyle: "tab",
23+
indentWidth: 2,
24+
lineWidth: 80,
25+
},
26+
linter: {
27+
enabled: false,
28+
},
29+
javascript: {
30+
formatter: {
31+
enabled: true,
32+
},
33+
},
34+
json: {
35+
formatter: {
36+
enabled: true,
37+
},
38+
},
39+
});
40+
41+
return { biome, projectKey };
42+
} catch (error) {
43+
consola.error("Failed to initialize Biome:", error);
44+
throw error;
45+
}
46+
}
47+
48+
function isSupportedFile(filePath: string): boolean {
49+
const ext = path.extname(filePath).toLowerCase();
50+
const supportedExtensions = [".js", ".jsx", ".ts", ".tsx", ".json", ".jsonc"];
51+
return supportedExtensions.includes(ext);
52+
}
53+
54+
function shouldSkipFile(filePath: string): boolean {
55+
const basename = path.basename(filePath);
56+
const skipPatterns = [
57+
".hbs",
58+
"package-lock.json",
59+
"yarn.lock",
60+
"pnpm-lock.yaml",
61+
"bun.lock",
62+
".d.ts",
63+
];
64+
65+
return skipPatterns.some((pattern) => basename.includes(pattern));
66+
}
67+
68+
export async function formatFileWithBiome(
69+
filePath: string,
70+
content: string,
71+
): Promise<string | null> {
72+
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) {
73+
return null;
74+
}
75+
76+
try {
77+
const { biome: biomeInstance, projectKey: key } = await initializeBiome();
78+
79+
const result = biomeInstance.formatContent(key, content, {
80+
filePath: path.basename(filePath),
81+
});
82+
83+
if (result.diagnostics && result.diagnostics.length > 0) {
84+
consola.debug(
85+
`Biome formatting diagnostics for ${filePath}:`,
86+
result.diagnostics,
87+
);
88+
}
89+
90+
return result.content;
91+
} catch (error) {
92+
consola.warn(`Failed to format ${filePath} with Biome:`, error);
93+
return null;
94+
}
95+
}

apps/cli/src/utils/template-processor.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,36 @@ import consola from "consola";
33
import fs from "fs-extra";
44
import handlebars from "handlebars";
55
import type { ProjectConfig } from "../types";
6+
import { formatFileWithBiome } from "./biome-formatter";
67

7-
/**
8-
* Processes a Handlebars template file and writes the output to the destination.
9-
* @param srcPath Path to the source .hbs template file.
10-
* @param destPath Path to write the processed file.
11-
* @param context Data to be passed to the Handlebars template.
12-
*/
138
export async function processTemplate(
149
srcPath: string,
1510
destPath: string,
1611
context: ProjectConfig,
1712
) {
1813
try {
19-
const templateContent = await fs.readFile(srcPath, "utf-8");
20-
const template = handlebars.compile(templateContent);
21-
const processedContent = template(context);
22-
2314
await fs.ensureDir(path.dirname(destPath));
24-
await fs.writeFile(destPath, processedContent);
15+
16+
let content: string;
17+
18+
if (srcPath.endsWith(".hbs")) {
19+
const templateContent = await fs.readFile(srcPath, "utf-8");
20+
const template = handlebars.compile(templateContent);
21+
content = template(context);
22+
} else {
23+
content = await fs.readFile(srcPath, "utf-8");
24+
}
25+
26+
try {
27+
const formattedContent = await formatFileWithBiome(destPath, content);
28+
if (formattedContent) {
29+
content = formattedContent;
30+
}
31+
} catch (formatError) {
32+
consola.debug(`Failed to format ${destPath}:`, formatError);
33+
}
34+
35+
await fs.writeFile(destPath, content);
2536
} catch (error) {
2637
consola.error(`Error processing template ${srcPath}:`, error);
2738
throw new Error(`Failed to process template ${srcPath}`);

0 commit comments

Comments
 (0)