feat: TS init 92594f86
Steve · 2025-05-16 10:40 6 file(s) · +516 −390
bun.lock +68 −0
13 13
        "ora": "^6.3.1",
14 14
        "prompts": "^2.4.2",
15 15
      },
16 +
      "devDependencies": {
17 +
        "@types/degit": "^2.8.6",
18 +
        "@types/figlet": "^1.7.0",
19 +
        "@types/fs-extra": "^11.0.4",
20 +
        "@types/inquirer": "^9.0.7",
21 +
        "@types/node": "^20.11.17",
22 +
        "@types/prompts": "^2.4.9",
23 +
        "ts-node": "^10.9.2",
24 +
        "typescript": "^5.3.3",
25 +
      },
16 26
    },
17 27
  },
18 28
  "packages": {
29 +
    "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
30 +
31 +
    "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
32 +
33 +
    "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
34 +
35 +
    "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
36 +
37 +
    "@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="],
38 +
39 +
    "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="],
40 +
41 +
    "@tsconfig/node14": ["@tsconfig/node14@1.0.3", "", {}, "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="],
42 +
43 +
    "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="],
44 +
45 +
    "@types/degit": ["@types/degit@2.8.6", "", {}, "sha512-y0M7sqzsnHB6cvAeTCBPrCQNQiZe8U4qdzf8uBVmOWYap5MMTN/gB2iEqrIqFiYcsyvP74GnGD5tgsHttielFw=="],
46 +
47 +
    "@types/figlet": ["@types/figlet@1.7.0", "", {}, "sha512-KwrT7p/8Eo3Op/HBSIwGXOsTZKYiM9NpWRBJ5sVjWP/SmlS+oxxRvJht/FNAtliJvja44N3ul1yATgohnVBV0Q=="],
48 +
49 +
    "@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="],
50 +
51 +
    "@types/inquirer": ["@types/inquirer@9.0.8", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-CgPD5kFGWsb8HJ5K7rfWlifao87m4ph8uioU7OTncJevmE/VLIqAAjfQtko578JZg7/f69K4FgqYym3gNr7DeA=="],
52 +
53 +
    "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="],
54 +
55 +
    "@types/node": ["@types/node@20.17.47", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ=="],
56 +
57 +
    "@types/prompts": ["@types/prompts@2.4.9", "", { "dependencies": { "@types/node": "*", "kleur": "^3.0.3" } }, "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA=="],
58 +
59 +
    "@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
60 +
61 +
    "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
62 +
63 +
    "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
64 +
19 65
    "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
66 +
67 +
    "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
20 68
21 69
    "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
22 70
34 82
35 83
    "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
36 84
85 +
    "create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
86 +
37 87
    "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
38 88
39 89
    "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
40 90
41 91
    "degit": ["degit@2.8.4", "", { "bin": { "degit": "degit" } }, "sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng=="],
42 92
93 +
    "diff": ["diff@4.0.2", "", {}, "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="],
94 +
43 95
    "execa": ["execa@7.2.0", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.1", "human-signals": "^4.3.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^3.0.7", "strip-final-newline": "^3.0.0" } }, "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA=="],
44 96
45 97
    "figlet": ["figlet@1.8.1", "", { "bin": { "figlet": "bin/index.js" } }, "sha512-kEC3Sme+YvA8Hkibv0NR1oClGcWia0VB2fC1SlMy027cwe795Xx40Xiv/nw/iFAwQLupymWh+uhAAErn/7hwPg=="],
70 122
71 123
    "log-symbols": ["log-symbols@5.1.0", "", { "dependencies": { "chalk": "^5.0.0", "is-unicode-supported": "^1.1.0" } }, "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA=="],
72 124
125 +
    "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
126 +
73 127
    "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
74 128
75 129
    "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
87 141
    "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
88 142
89 143
    "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
144 +
145 +
    "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
90 146
91 147
    "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
92 148
106 162
107 163
    "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
108 164
165 +
    "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="],
166 +
167 +
    "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
168 +
169 +
    "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
170 +
171 +
    "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
172 +
109 173
    "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
110 174
111 175
    "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
112 176
177 +
    "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
178 +
113 179
    "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
114 180
115 181
    "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
182 +
183 +
    "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
116 184
117 185
    "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
118 186
index.js (deleted) +0 −385
1 -
#!/usr/bin/env node
2 -
3 -
import fs from "fs-extra";
4 -
import path from "path";
5 -
import { fileURLToPath } from "url";
6 -
import prompts from "prompts";
7 -
import { program } from "commander";
8 -
import chalk from "chalk";
9 -
import ora from "ora";
10 -
import { execa } from "execa";
11 -
import degit from "degit";
12 -
import figlet from "figlet";
13 -
import {
14 -
	defaultTemplate,
15 -
	shadcnTemplate,
16 -
	tailwindTemplate,
17 -
	honoRpcTemplate,
18 -
} from "./utils/templates.js";
19 -
20 -
const __filename = fileURLToPath(import.meta.url);
21 -
const __dirname = path.dirname(__filename);
22 -
23 -
// GitHub repository for the template
24 -
const DEFAULT_REPO = "stevedylandev/bhvr";
25 -
26 -
// Available templates
27 -
const TEMPLATES = {
28 -
	default: {
29 -
		branch: "main",
30 -
		description: "Basic setup with Bun, Hono, Vite and React",
31 -
	},
32 -
	tailwind: { branch: "tailwindcss", description: "Basic setup + TailwindCSS" },
33 -
	shadcn: {
34 -
		branch: "shadcn-ui",
35 -
		description: "Basic setup + TailwindCSS + shadcn/ui",
36 -
	},
37 -
};
38 -
39 -
// Function to display a fun banner
40 -
function displayBanner() {
41 -
	const text = figlet.textSync("bhvr", {
42 -
		font: "Big",
43 -
		horizontalLayout: "default",
44 -
		verticalLayout: "default",
45 -
		width: 80,
46 -
		whitespaceBreak: true,
47 -
	});
48 -
49 -
	console.log("\n");
50 -
	console.log(chalk.yellowBright(text));
51 -
	console.log(`\n${chalk.cyan("🦫 Lets build 🦫")}\n`);
52 -
	console.log(`${chalk.blue("https://github.com/stevedylandev/bhvr")}\n`);
53 -
}
54 -
55 -
// Set up the CLI program
56 -
program
57 -
	.name("create-bhvr")
58 -
	.description("Create a bhvr monorepo starter project")
59 -
	.argument("[project-directory]", "directory to create the project in")
60 -
	.option("-y, --yes", "skip confirmation prompts")
61 -
	.option("--ts, --typescript", "use TypeScript (default)")
62 -
	.option(
63 -
		"--repo <repo>",
64 -
		"specify a custom GitHub repository as source",
65 -
		DEFAULT_REPO,
66 -
	)
67 -
	.option(
68 -
		"--template <template>",
69 -
		"specify a template (default, tailwind, shadcn)",
70 -
		"default",
71 -
	)
72 -
	.option("--branch <branch>", "specify a branch to use from the repository")
73 -
	.option("--rpc", "use Hono RPC client for type-safe API communication")
74 -
	.action(async (projectDirectory, options) => {
75 -
		try {
76 -
			displayBanner();
77 -
			const result = await createProject(projectDirectory, options);
78 -
			if (result) {
79 -
				console.log(chalk.green.bold("🎉 Project created successfully!"));
80 -
				console.log("\nNext steps:");
81 -
82 -
				if (!result.dependenciesInstalled) {
83 -
					console.log(chalk.cyan(`  cd ${result.projectName}`));
84 -
					console.log(chalk.cyan("  bun install"));
85 -
				} else {
86 -
					console.log(chalk.cyan(`  cd ${result.projectName}`));
87 -
				}
88 -
89 -
				console.log(chalk.cyan("  bun run dev:client   # Start the client"));
90 -
				console.log(
91 -
					chalk.cyan(
92 -
						"  bun run dev:server   # Start the server in another terminal",
93 -
					),
94 -
				);
95 -
				console.log(chalk.cyan("  bun run dev          # Start all"));
96 -
				process.exit(0);
97 -
			}
98 -
		} catch (err) {
99 -
			console.error(chalk.red("Error creating project:"), err);
100 -
			process.exit(1);
101 -
		}
102 -
	});
103 -
104 -
program.parse();
105 -
106 -
async function createProject(projectDirectory, options) {
107 -
	// If project directory not provided, prompt for it
108 -
	let projectName = projectDirectory;
109 -
110 -
	if (!projectName && !options.yes) {
111 -
		const response = await prompts({
112 -
			type: "text",
113 -
			name: "projectName",
114 -
			message: "What is the name of your project?",
115 -
			initial: "my-bhvr-app",
116 -
		});
117 -
118 -
		if (!response.projectName) {
119 -
			console.log(chalk.yellow("Project creation cancelled."));
120 -
			return null;
121 -
		}
122 -
123 -
		projectName = response.projectName;
124 -
	} else if (!projectName) {
125 -
		projectName = "my-bhvr-app";
126 -
	}
127 -
128 -
	// Template selection
129 -
	let templateChoice = options.template;
130 -
131 -
	if (!options.yes && !options.branch) {
132 -
		const templateChoices = Object.keys(TEMPLATES).map((key) => ({
133 -
			title: `${key} (${TEMPLATES[key].description})`,
134 -
			value: key,
135 -
		}));
136 -
137 -
		const templateResponse = await prompts({
138 -
			type: "select",
139 -
			name: "template",
140 -
			message: "Select a template:",
141 -
			choices: templateChoices,
142 -
			initial: 0,
143 -
		});
144 -
145 -
		if (templateResponse.template === undefined) {
146 -
			console.log(chalk.yellow("Project creation cancelled."));
147 -
			return null;
148 -
		}
149 -
150 -
		templateChoice = templateResponse.template;
151 -
	}
152 -
153 -
	// Create the project directory
154 -
	const projectPath = path.resolve(process.cwd(), projectName);
155 -
156 -
	// Check if directory exists and is not empty
157 -
	if (fs.existsSync(projectPath)) {
158 -
		const files = fs.readdirSync(projectPath);
159 -
160 -
		if (files.length > 0 && !options.yes) {
161 -
			const { overwrite } = await prompts({
162 -
				type: "confirm",
163 -
				name: "overwrite",
164 -
				message: `The directory ${projectName} already exists and is not empty. Do you want to overwrite it?`,
165 -
				initial: false,
166 -
			});
167 -
168 -
			if (!overwrite) {
169 -
				console.log(chalk.yellow("Project creation cancelled."));
170 -
				return null;
171 -
			}
172 -
173 -
			// Clear directory if overwriting
174 -
			await fs.emptyDir(projectPath);
175 -
		}
176 -
	}
177 -
178 -
	// Create directory if it doesn't exist
179 -
	fs.ensureDirSync(projectPath);
180 -
181 -
	// Clone template from GitHub
182 -
	const repoPath = options.repo || DEFAULT_REPO;
183 -
	// Use provided branch, template branch, or default
184 -
	const branch =
185 -
		options.branch ||
186 -
		(TEMPLATES[templateChoice] ? TEMPLATES[templateChoice].branch : "main");
187 -
	const repoUrl = `${repoPath}#${branch}`;
188 -
189 -
	const spinner = ora("Downloading template...").start();
190 -
191 -
	try {
192 -
		const emitter = degit(repoUrl, {
193 -
			cache: false,
194 -
			force: true,
195 -
			verbose: false,
196 -
		});
197 -
198 -
		await emitter.clone(projectPath);
199 -
		spinner.succeed(
200 -
			`Template downloaded successfully (${templateChoice} template)`,
201 -
		);
202 -
203 -
		// Update package.json with project name
204 -
		const pkgJsonPath = path.join(projectPath, "package.json");
205 -
		if (fs.existsSync(pkgJsonPath)) {
206 -
			const pkgJson = await fs.readJson(pkgJsonPath);
207 -
			pkgJson.name = projectName;
208 -
			await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
209 -
		}
210 -
211 -
		// Remove the .git directory if it exists
212 -
		const gitDir = path.join(projectPath, ".git");
213 -
		if (fs.existsSync(gitDir)) {
214 -
			await fs.remove(gitDir);
215 -
			console.log(chalk.blue("Removed .git directory"));
216 -
		}
217 -
218 -
		if (options.rpc) {
219 -
			await patchFilesForRPC(projectPath);
220 -
		}
221 -
222 -
		// Initialize git repository?
223 -
		let gitInitialized = false;
224 -
225 -
		if (!options.yes) {
226 -
			const gitResponse = await prompts({
227 -
				type: "confirm",
228 -
				name: "initGit",
229 -
				message: "Initialize a git repository?",
230 -
				initial: true,
231 -
			});
232 -
233 -
			if (gitResponse.initGit) {
234 -
				try {
235 -
					spinner.start("Initializing git repository...");
236 -
					await execa("git", ["init"], { cwd: projectPath });
237 -
					spinner.succeed("Git repository initialized");
238 -
					gitInitialized = true;
239 -
				} catch (err) {
240 -
					spinner.fail(
241 -
						"Failed to initialize git repository. Is git installed?",
242 -
					);
243 -
					console.error(chalk.red("Git error:"), err.message);
244 -
				}
245 -
			}
246 -
		} else {
247 -
			// If using --yes, automatically initialize git
248 -
			try {
249 -
				spinner.start("Initializing git repository...");
250 -
				await execa("git", ["init"], { cwd: projectPath });
251 -
				spinner.succeed("Git repository initialized");
252 -
				gitInitialized = true;
253 -
			} catch (err) {
254 -
				spinner.fail("Failed to initialize git repository. Is git installed?");
255 -
			}
256 -
		}
257 -
258 -
		// Install dependencies?
259 -
		let dependenciesInstalled = false;
260 -
261 -
		if (!options.yes) {
262 -
			const depsResponse = await prompts({
263 -
				type: "confirm",
264 -
				name: "installDeps",
265 -
				message: "Install dependencies?",
266 -
				initial: true,
267 -
			});
268 -
269 -
			if (depsResponse.installDeps) {
270 -
				spinner.start("Installing dependencies...");
271 -
				try {
272 -
					// Try with bun first
273 -
					await execa("bun", ["install"], { cwd: projectPath });
274 -
					spinner.succeed("Dependencies installed with bun");
275 -
					dependenciesInstalled = true;
276 -
				} catch (bunErr) {
277 -
					// If bun fails, try with npm
278 -
					try {
279 -
						spinner.text = "Installing dependencies with npm...";
280 -
						await execa("npm", ["install"], { cwd: projectPath });
281 -
						spinner.succeed("Dependencies installed with npm");
282 -
						dependenciesInstalled = true;
283 -
					} catch (npmErr) {
284 -
						spinner.fail("Failed to install dependencies.");
285 -
						console.log(
286 -
							chalk.yellow(
287 -
								"You can install them manually after navigating to the project directory.",
288 -
							),
289 -
						);
290 -
					}
291 -
				}
292 -
			}
293 -
		} else {
294 -
			// If using --yes, automatically install dependencies
295 -
			spinner.start("Installing dependencies...");
296 -
			try {
297 -
				await execa("bun", ["install"], { cwd: projectPath });
298 -
				spinner.succeed("Dependencies installed with bun");
299 -
				dependenciesInstalled = true;
300 -
			} catch (bunErr) {
301 -
				try {
302 -
					spinner.text = "Installing dependencies with npm...";
303 -
					await execa("npm", ["install"], { cwd: projectPath });
304 -
					spinner.succeed("Dependencies installed with npm");
305 -
					dependenciesInstalled = true;
306 -
				} catch (npmErr) {
307 -
					spinner.fail(
308 -
						"Failed to install dependencies. You can install them manually later.",
309 -
					);
310 -
				}
311 -
			}
312 -
		}
313 -
314 -
		if (!options.yes && !options.rpc) {
315 -
			const { useRpc } = await prompts({
316 -
				type: "confirm",
317 -
				name: "useRpc",
318 -
				message: "Use Hono RPC client for type-safe API communication?",
319 -
				initial: false,
320 -
			});
321 -
322 -
			if (useRpc) {
323 -
				await patchFilesForRPC(projectPath, templateChoice);
324 -
			}
325 -
		}
326 -
327 -
		return {
328 -
			projectName,
329 -
			gitInitialized,
330 -
			dependenciesInstalled,
331 -
			template: templateChoice,
332 -
		};
333 -
	} catch (err) {
334 -
		spinner.fail("Failed to download template");
335 -
		throw err;
336 -
	}
337 -
}
338 -
339 -
async function patchFilesForRPC(projectPath, templateChoice) {
340 -
	const spinner = ora("Setting up RPC client...").start();
341 -
342 -
	try {
343 -
		// 1. Update client package.json to ensure hono client is installed
344 -
		const clientPkgPath = path.join(projectPath, "client", "package.json");
345 -
		const clientPkg = await fs.readJson(clientPkgPath);
346 -
347 -
		if (!clientPkg.dependencies.hono) {
348 -
			await execa("bun", ["install", "hono"], { cwd: projectPath });
349 -
		}
350 -
351 -
		await fs.writeJson(clientPkgPath, clientPkg, { spaces: 2 });
352 -
353 -
		// 2. Server modification for RPC export type
354 -
		const serverIndexPath = path.join(projectPath, "server", "src", "index.ts");
355 -
		await fs.writeFile(serverIndexPath, honoRpcTemplate, "utf8");
356 -
357 -
		// 3. Update App.tsx based on template selection using switch statement
358 -
		const appTsxPath = path.join(projectPath, "client", "src", "App.tsx");
359 -
360 -
		// Determine template content based on the template type
361 -
		let updatedAppContent;
362 -
363 -
		// Select template based on choice
364 -
		switch (templateChoice) {
365 -
			case "shadcn":
366 -
				updatedAppContent = shadcnTemplate;
367 -
				break;
368 -
			case "tailwind":
369 -
				updatedAppContent = tailwindTemplate;
370 -
				break;
371 -
			case "default":
372 -
			default:
373 -
				updatedAppContent = defaultTemplate;
374 -
				break;
375 -
		}
376 -
377 -
		await fs.writeFile(appTsxPath, updatedAppContent, "utf8");
378 -
		spinner.succeed("RPC client setup completed");
379 -
		return true;
380 -
	} catch (err) {
381 -
		spinner.fail("Failed to set up RPC client");
382 -
		console.error(chalk.red("Error:"), err.message);
383 -
		return false;
384 -
	}
385 -
}
package.json +17 −5
2 2
	"name": "create-bhvr",
3 3
	"version": "0.3.4",
4 4
	"description": "Create a new bhvr project",
5 -
	"main": "index.js",
6 5
	"type": "module",
6 +
	"main": "./dist/index.js",
7 +
	"types": "./dist/index.d.ts",
7 8
	"bin": {
8 -
		"create-bhvr": "index.js"
9 +
		"create-bhvr": "./dist/index.js"
9 10
	},
10 -
	"files": ["index.js", "utils"],
11 +
	"files": ["dist"],
11 12
	"scripts": {
12 -
		"test": "echo \"Error: no test specified\" && exit 1"
13 +
		"build": "bun build src/index.ts --outdir dist --target node",
14 +
		"start": "bun ./dist/index.js"
13 15
	},
14 16
	"keywords": [
15 17
		"bun",
23 25
	],
24 26
	"author": "Steve Simkins",
25 27
	"license": "MIT",
28 +
	"devDependencies": {
29 +
		"@types/degit": "^2.8.6",
30 +
		"@types/figlet": "^1.7.0",
31 +
		"@types/fs-extra": "^11.0.4",
32 +
		"@types/inquirer": "^9.0.7",
33 +
		"@types/node": "^20.11.17",
34 +
		"@types/prompts": "^2.4.9",
35 +
		"ts-node": "^10.9.2",
36 +
		"typescript": "^5.3.3"
37 +
	},
26 38
	"dependencies": {
27 39
		"chalk": "^5.3.0",
28 40
		"commander": "^11.1.0",
34 46
		"prompts": "^2.4.2"
35 47
	},
36 48
	"engines": {
37 -
		"node": ">=14.16"
49 +
		"node": ">=18"
38 50
	}
39 51
}
src/index.ts (added) +403 −0
1 +
import fs from "fs-extra";
2 +
import path from "node:path";
3 +
import { fileURLToPath } from "node:url";
4 +
import prompts from "prompts";
5 +
import { program } from "commander";
6 +
import chalk from "chalk";
7 +
import ora from "ora";
8 +
import { execa } from "execa";
9 +
import degit from "degit";
10 +
import figlet from "figlet";
11 +
import {
12 +
	defaultTemplate,
13 +
	shadcnTemplate,
14 +
	tailwindTemplate,
15 +
	honoRpcTemplate,
16 +
} from "./utils/templates";
17 +
18 +
const __filename = fileURLToPath(import.meta.url);
19 +
const __dirname = path.dirname(__filename);
20 +
21 +
// GitHub repository for the template
22 +
const DEFAULT_REPO = "stevedylandev/bhvr";
23 +
24 +
interface TemplateInfo {
25 +
	branch: string;
26 +
	description: string;
27 +
}
28 +
29 +
const TEMPLATES: Record<string, TemplateInfo> = {
30 +
	default: {
31 +
		branch: "main",
32 +
		description: "Basic setup with Bun, Hono, Vite and React",
33 +
	},
34 +
	tailwind: { branch: "tailwindcss", description: "Basic setup + TailwindCSS" },
35 +
	shadcn: {
36 +
		branch: "shadcn-ui",
37 +
		description: "Basic setup + TailwindCSS + shadcn/ui",
38 +
	},
39 +
};
40 +
41 +
interface ProjectOptions {
42 +
	yes?: boolean;
43 +
	typescript?: boolean;
44 +
	repo?: string;
45 +
	template?: string;
46 +
	branch?: string;
47 +
	rpc?: boolean;
48 +
}
49 +
50 +
function displayBanner() {
51 +
	const text = figlet.textSync("bhvr", {
52 +
		font: "Big",
53 +
		horizontalLayout: "default",
54 +
		verticalLayout: "default",
55 +
		width: 80,
56 +
		whitespaceBreak: true,
57 +
	});
58 +
59 +
	console.log("\n");
60 +
	console.log(chalk.yellowBright(text));
61 +
	console.log(`\n${chalk.cyan("🦫 Lets build 🦫")}\n`);
62 +
	console.log(`${chalk.blue("https://github.com/stevedylandev/bhvr")}\n`);
63 +
}
64 +
65 +
program
66 +
	.name("create-bhvr")
67 +
	.description("Create a bhvr monorepo starter project")
68 +
	.argument("[project-directory]", "directory to create the project in")
69 +
	.option("-y, --yes", "skip confirmation prompts")
70 +
	.option("--ts, --typescript", "use TypeScript (default)")
71 +
	.option(
72 +
		"--repo <repo>",
73 +
		"specify a custom GitHub repository as source",
74 +
		DEFAULT_REPO,
75 +
	)
76 +
	.option(
77 +
		"--template <template>",
78 +
		"specify a template (default, tailwind, shadcn)",
79 +
		"default",
80 +
	)
81 +
	.option("--branch <branch>", "specify a branch to use from the repository")
82 +
	.option("--rpc", "use Hono RPC client for type-safe API communication")
83 +
	.action(async (projectDirectory, options) => {
84 +
		try {
85 +
			displayBanner();
86 +
			const result = await createProject(projectDirectory, options);
87 +
			if (result) {
88 +
				console.log(chalk.green.bold("🎉 Project created successfully!"));
89 +
				console.log("\nNext steps:");
90 +
91 +
				if (!result.dependenciesInstalled) {
92 +
					console.log(chalk.cyan(`  cd ${result.projectName}`));
93 +
					console.log(chalk.cyan("  bun install"));
94 +
				} else {
95 +
					console.log(chalk.cyan(`  cd ${result.projectName}`));
96 +
				}
97 +
98 +
				console.log(chalk.cyan("  bun run dev:client   # Start the client"));
99 +
				console.log(
100 +
					chalk.cyan(
101 +
						"  bun run dev:server   # Start the server in another terminal",
102 +
					),
103 +
				);
104 +
				console.log(chalk.cyan("  bun run dev          # Start all"));
105 +
				process.exit(0);
106 +
			}
107 +
		} catch (err) {
108 +
			console.error(chalk.red("Error creating project:"), err);
109 +
			process.exit(1);
110 +
		}
111 +
	});
112 +
113 +
program.parse();
114 +
115 +
interface ProjectResult {
116 +
	projectName: string;
117 +
	gitInitialized: boolean;
118 +
	dependenciesInstalled: boolean;
119 +
	template: string;
120 +
}
121 +
122 +
async function createProject(
123 +
	projectDirectory: string,
124 +
	options: ProjectOptions,
125 +
): Promise<ProjectResult | null> {
126 +
	let projectName = projectDirectory;
127 +
128 +
	if (!projectName && !options.yes) {
129 +
		const response = await prompts({
130 +
			type: "text",
131 +
			name: "projectName",
132 +
			message: "What is the name of your project?",
133 +
			initial: "my-bhvr-app",
134 +
		});
135 +
136 +
		if (!response.projectName) {
137 +
			console.log(chalk.yellow("Project creation cancelled."));
138 +
			return null;
139 +
		}
140 +
141 +
		projectName = response.projectName;
142 +
	} else if (!projectName) {
143 +
		projectName = "my-bhvr-app";
144 +
	}
145 +
146 +
	let templateChoice = options.template || "default";
147 +
148 +
	if (!options.yes && !options.branch) {
149 +
		const templateChoices = Object.keys(TEMPLATES).map((key) => ({
150 +
			title: `${key} (${TEMPLATES[key]?.description})`,
151 +
			value: key,
152 +
		}));
153 +
154 +
		const templateResponse = await prompts({
155 +
			type: "select",
156 +
			name: "template",
157 +
			message: "Select a template:",
158 +
			choices: templateChoices,
159 +
			initial: 0,
160 +
		});
161 +
162 +
		if (templateResponse.template === undefined) {
163 +
			console.log(chalk.yellow("Project creation cancelled."));
164 +
			return null;
165 +
		}
166 +
167 +
		templateChoice = templateResponse.template;
168 +
	}
169 +
170 +
	const projectPath = path.resolve(process.cwd(), projectName);
171 +
172 +
	if (fs.existsSync(projectPath)) {
173 +
		const files = fs.readdirSync(projectPath);
174 +
175 +
		if (files.length > 0 && !options.yes) {
176 +
			const { overwrite } = await prompts({
177 +
				type: "confirm",
178 +
				name: "overwrite",
179 +
				message: `The directory ${projectName} already exists and is not empty. Do you want to overwrite it?`,
180 +
				initial: false,
181 +
			});
182 +
183 +
			if (!overwrite) {
184 +
				console.log(chalk.yellow("Project creation cancelled."));
185 +
				return null;
186 +
			}
187 +
188 +
			await fs.emptyDir(projectPath);
189 +
		}
190 +
	}
191 +
192 +
	fs.ensureDirSync(projectPath);
193 +
194 +
	const repoPath = options.repo || DEFAULT_REPO;
195 +
	const templateConfig =
196 +
		TEMPLATES[templateChoice as keyof typeof TEMPLATES] || TEMPLATES.default;
197 +
	const branch = options.branch || (templateConfig?.branch ?? "main");
198 +
	const repoUrl = `${repoPath}#${branch}`;
199 +
200 +
	const spinner = ora("Downloading template...").start();
201 +
202 +
	try {
203 +
		const emitter = degit(repoUrl, {
204 +
			cache: false,
205 +
			force: true,
206 +
			verbose: false,
207 +
		});
208 +
209 +
		await emitter.clone(projectPath);
210 +
		spinner.succeed(
211 +
			`Template downloaded successfully (${templateChoice} template)`,
212 +
		);
213 +
214 +
		const pkgJsonPath = path.join(projectPath, "package.json");
215 +
		if (fs.existsSync(pkgJsonPath)) {
216 +
			const pkgJson = await fs.readJson(pkgJsonPath);
217 +
			pkgJson.name = projectName;
218 +
			await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
219 +
		}
220 +
221 +
		const gitDir = path.join(projectPath, ".git");
222 +
		if (fs.existsSync(gitDir)) {
223 +
			await fs.remove(gitDir);
224 +
			console.log(chalk.blue("Removed .git directory"));
225 +
		}
226 +
227 +
		let useRpc = options.rpc;
228 +
229 +
		if (!options.yes && !options.rpc) {
230 +
			const rpcResponse = await prompts({
231 +
				type: "confirm",
232 +
				name: "useRpc",
233 +
				message: "Use Hono RPC client for type-safe API communication?",
234 +
				initial: false,
235 +
			});
236 +
237 +
			if (rpcResponse.useRpc === undefined) {
238 +
				console.log(chalk.yellow("Project creation cancelled."));
239 +
				return null;
240 +
			}
241 +
242 +
			useRpc = rpcResponse.useRpc;
243 +
		}
244 +
245 +
		if (useRpc) {
246 +
			await patchFilesForRPC(projectPath, templateChoice);
247 +
		}
248 +
249 +
		let gitInitialized = false;
250 +
251 +
		if (!options.yes) {
252 +
			const gitResponse = await prompts({
253 +
				type: "confirm",
254 +
				name: "initGit",
255 +
				message: "Initialize a git repository?",
256 +
				initial: true,
257 +
			});
258 +
259 +
			if (gitResponse.initGit) {
260 +
				try {
261 +
					spinner.start("Initializing git repository...");
262 +
					await execa("git", ["init"], { cwd: projectPath });
263 +
					spinner.succeed("Git repository initialized");
264 +
					gitInitialized = true;
265 +
				} catch (err: unknown) {
266 +
					spinner.fail(
267 +
						"Failed to initialize git repository. Is git installed?",
268 +
					);
269 +
					if (err instanceof Error) {
270 +
						console.error(chalk.red("Git error:"), err.message);
271 +
					} else {
272 +
						console.error(chalk.red("Git error: Unknown error"));
273 +
					}
274 +
				}
275 +
			}
276 +
		} else {
277 +
			try {
278 +
				spinner.start("Initializing git repository...");
279 +
				await execa("git", ["init"], { cwd: projectPath });
280 +
				spinner.succeed("Git repository initialized");
281 +
				gitInitialized = true;
282 +
			} catch (err) {
283 +
				spinner.fail("Failed to initialize git repository. Is git installed?");
284 +
			}
285 +
		}
286 +
287 +
		let dependenciesInstalled = false;
288 +
289 +
		if (!options.yes) {
290 +
			const depsResponse = await prompts({
291 +
				type: "confirm",
292 +
				name: "installDeps",
293 +
				message: "Install dependencies?",
294 +
				initial: true,
295 +
			});
296 +
297 +
			if (depsResponse.installDeps) {
298 +
				spinner.start("Installing dependencies...");
299 +
				try {
300 +
					await execa("bun", ["install"], { cwd: projectPath });
301 +
					spinner.succeed("Dependencies installed with bun");
302 +
					dependenciesInstalled = true;
303 +
				} catch (bunErr) {
304 +
					try {
305 +
						spinner.text = "Installing dependencies with npm...";
306 +
						await execa("npm", ["install"], { cwd: projectPath });
307 +
						spinner.succeed("Dependencies installed with npm");
308 +
						dependenciesInstalled = true;
309 +
					} catch (npmErr) {
310 +
						spinner.fail("Failed to install dependencies.");
311 +
						console.log(
312 +
							chalk.yellow(
313 +
								"You can install them manually after navigating to the project directory.",
314 +
							),
315 +
						);
316 +
					}
317 +
				}
318 +
			}
319 +
		} else {
320 +
			spinner.start("Installing dependencies...");
321 +
			try {
322 +
				await execa("bun", ["install"], { cwd: projectPath });
323 +
				spinner.succeed("Dependencies installed with bun");
324 +
				dependenciesInstalled = true;
325 +
			} catch (bunErr) {
326 +
				try {
327 +
					spinner.text = "Installing dependencies with npm...";
328 +
					await execa("npm", ["install"], { cwd: projectPath });
329 +
					spinner.succeed("Dependencies installed with npm");
330 +
					dependenciesInstalled = true;
331 +
				} catch (npmErr) {
332 +
					spinner.fail(
333 +
						"Failed to install dependencies. You can install them manually later.",
334 +
					);
335 +
				}
336 +
			}
337 +
		}
338 +
339 +
		return {
340 +
			projectName,
341 +
			gitInitialized,
342 +
			dependenciesInstalled,
343 +
			template: templateChoice,
344 +
		};
345 +
	} catch (err) {
346 +
		spinner.fail("Failed to download template");
347 +
		throw err;
348 +
	}
349 +
}
350 +
351 +
async function patchFilesForRPC(
352 +
	projectPath: string,
353 +
	templateChoice: string,
354 +
): Promise<boolean> {
355 +
	const spinner = ora("Setting up RPC client...").start();
356 +
357 +
	try {
358 +
		// 1. Update client package.json to ensure hono client is installed
359 +
		const clientPkgPath = path.join(projectPath, "client", "package.json");
360 +
		const clientPkg = await fs.readJson(clientPkgPath);
361 +
362 +
		if (!clientPkg.dependencies.hono) {
363 +
			await execa("bun", ["install", "hono"], { cwd: projectPath });
364 +
		}
365 +
366 +
		await fs.writeJson(clientPkgPath, clientPkg, { spaces: 2 });
367 +
368 +
		// 2. Server modification for RPC export type
369 +
		const serverIndexPath = path.join(projectPath, "server", "src", "index.ts");
370 +
		await fs.writeFile(serverIndexPath, honoRpcTemplate, "utf8");
371 +
372 +
		// 3. Update App.tsx based on template selection using switch statement
373 +
		const appTsxPath = path.join(projectPath, "client", "src", "App.tsx");
374 +
375 +
		// Determine template content based on the template type
376 +
		let updatedAppContent: string;
377 +
378 +
		// Select template based on choice
379 +
		switch (templateChoice) {
380 +
			case "shadcn":
381 +
				updatedAppContent = shadcnTemplate;
382 +
				break;
383 +
			case "tailwind":
384 +
				updatedAppContent = tailwindTemplate;
385 +
				break;
386 +
			default:
387 +
				updatedAppContent = defaultTemplate;
388 +
				break;
389 +
		}
390 +
391 +
		await fs.writeFile(appTsxPath, updatedAppContent, "utf8");
392 +
		spinner.succeed("RPC client setup completed");
393 +
		return true;
394 +
	} catch (err: unknown) {
395 +
		spinner.fail("Failed to set up RPC client");
396 +
		if (err instanceof Error) {
397 +
			console.error(chalk.red("Error:"), err.message);
398 +
		} else {
399 +
			console.error(chalk.red("Error: Unknown error"));
400 +
		}
401 +
		return false;
402 +
	}
403 +
}
tsconfig.json (added) +28 −0
1 +
{
2 +
	"compilerOptions": {
3 +
		// Environment setup & latest features
4 +
		"lib": ["ESNext"],
5 +
		"target": "ESNext",
6 +
		"module": "ESNext",
7 +
		"moduleResolution": "node", // Add this for ESM support
8 +
		"allowJs": true,
9 +
		"composite": false,
10 +
		"declaration": true,
11 +
		"outDir": "./dist",
12 +
		"noEmit": false,
13 +
		"allowSyntheticDefaultImports": true,
14 +
		"esModuleInterop": true,
15 +
		// Best practices
16 +
		"strict": true,
17 +
		"skipLibCheck": true,
18 +
		"noFallthroughCasesInSwitch": true,
19 +
		"noUncheckedIndexedAccess": true,
20 +
21 +
		// Some stricter flags (disabled by default)
22 +
		"noUnusedLocals": false,
23 +
		"noUnusedParameters": false,
24 +
		"noPropertyAccessFromIndexSignature": false
25 +
	},
26 +
	"include": ["src/**/*"],
27 +
	"exclude": ["node_modules", "dist", "examples/**/*"]
28 +
}
utils/templates.js → src/utils/templates.ts +0 −0