create-rotor 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # create-rotor
2
+
3
+ Scaffold Next.js projects with Bun, Tailwind CSS, Biome, and more.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ bunx create-rotor my-app
9
+ ```
10
+
11
+ Or with npx:
12
+
13
+ ```bash
14
+ npx create-rotor my-app
15
+ ```
16
+
17
+ The CLI will guide you through selecting optional features, installing dependencies, and initializing a git repository.
18
+
19
+ ## What's Included
20
+
21
+ Every generated project comes with:
22
+
23
+ - [Next.js](https://nextjs.org) with Turbopack
24
+ - [Tailwind CSS](https://tailwindcss.com) v4
25
+ - [Biome](https://biomejs.dev) for linting, formatting, and import sorting
26
+ - [Geist](https://vercel.com/font) font family
27
+ - [Husky](https://typicode.github.io/husky) + [lint-staged](https://github.com/lint-staged/lint-staged) for pre-commit checks
28
+ - TypeScript
29
+
30
+ ## Optional Features
31
+
32
+ | Feature | Description |
33
+ |---|---|
34
+ | **shadcn/ui** | UI component library built on Base UI |
35
+ | **SWR** | Data fetching and caching |
36
+ | **Drizzle + Supabase** | Database ORM with PostgreSQL |
37
+ | **Vercel AI SDK** | AI integration with OpenAI |
38
+
39
+ ## Biome Configuration
40
+
41
+ Generated projects include a strict Biome setup beyond the recommended defaults:
42
+
43
+ - **Code quality** — `noUnusedImports`, `noExplicitAny`, `noNonNullAssertion`, `noBarrelFile`
44
+ - **Style** — `useImportType`, `useConsistentArrayType`, `useForOf`, `useFilenamingConvention` (kebab-case)
45
+ - **Tailwind** — `useSortedClasses` with `cn`/`clsx`/`cva` support
46
+ - **Assist** — Auto import sorting via `organizeImports`
47
+
48
+ ## Options
49
+
50
+ ```
51
+ Usage: create-rotor [project-name]
52
+
53
+ Options:
54
+ -v, --version Show version
55
+ -h, --help Show help
56
+ ```
57
+
58
+ ## License
59
+
60
+ MIT
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ var require_src = __commonJS((exports, module) => {
91
91
 
92
92
  // src/index.ts
93
93
  import { execSync } from "node:child_process";
94
- import { cpSync, existsSync as existsSync2, renameSync, rmSync as rmSync2 } from "node:fs";
94
+ import { cpSync, existsSync as existsSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync2 } from "node:fs";
95
95
  import { join as join2, resolve } from "node:path";
96
96
 
97
97
  // node_modules/@clack/core/dist/index.mjs
@@ -1206,7 +1206,6 @@ var MODULES = {
1206
1206
  envMarker: "ai"
1207
1207
  }
1208
1208
  };
1209
- var VERSION = "0.1.3";
1210
1209
 
1211
1210
  // src/helpers.ts
1212
1211
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -1286,12 +1285,7 @@ function removeModuleFiles(projectDir, selectedModules) {
1286
1285
  }
1287
1286
  }
1288
1287
  function replaceProjectName(projectDir, projectName) {
1289
- const filesToReplace = [
1290
- "package.json",
1291
- "app/layout.tsx",
1292
- "app/page.tsx",
1293
- "README.md"
1294
- ];
1288
+ const filesToReplace = ["package.json", "app/layout.tsx", "README.md"];
1295
1289
  for (const file of filesToReplace) {
1296
1290
  const filePath = join(projectDir, file);
1297
1291
  if (!existsSync(filePath))
@@ -1333,6 +1327,10 @@ function trimCssShadcn(cssPath) {
1333
1327
  function getTemplatePath() {
1334
1328
  return resolve(import.meta.dirname, "..", "template");
1335
1329
  }
1330
+ function getVersion() {
1331
+ const pkgPath = resolve(import.meta.dirname, "..", "package.json");
1332
+ return JSON.parse(readFileSync2(pkgPath, "utf-8")).version;
1333
+ }
1336
1334
  function validateProjectName(name) {
1337
1335
  if (!name)
1338
1336
  return "Project name is required";
@@ -1344,12 +1342,12 @@ function validateProjectName(name) {
1344
1342
  async function main() {
1345
1343
  const args = process.argv.slice(2);
1346
1344
  if (args.includes("--version") || args.includes("-v")) {
1347
- console.log(VERSION);
1345
+ console.log(getVersion());
1348
1346
  process.exit(0);
1349
1347
  }
1350
1348
  if (args.includes("--help") || args.includes("-h")) {
1351
1349
  console.log(`
1352
- create-rotor v${VERSION}
1350
+ create-rotor v${getVersion()}
1353
1351
 
1354
1352
  Usage: create-rotor [project-name]
1355
1353
 
@@ -1409,6 +1407,14 @@ async function main() {
1409
1407
  Nt("Cancelled.");
1410
1408
  process.exit(0);
1411
1409
  }
1410
+ const installDeps = await Rt({
1411
+ message: "Install dependencies?",
1412
+ initialValue: true
1413
+ });
1414
+ if (Ct(installDeps)) {
1415
+ Nt("Cancelled.");
1416
+ process.exit(0);
1417
+ }
1412
1418
  const s = be();
1413
1419
  s.start("Creating project...");
1414
1420
  const templatePath = getTemplatePath();
@@ -1422,16 +1428,34 @@ async function main() {
1422
1428
  if (!selectedModules.includes("shadcn")) {
1423
1429
  trimCssShadcn(join2(targetDir, "app", "globals.css"));
1424
1430
  }
1431
+ s.stop("Project created!");
1432
+ if (installDeps) {
1433
+ const installSpinner = be();
1434
+ installSpinner.start("Installing dependencies...");
1435
+ try {
1436
+ execSync("bun install", { cwd: targetDir, stdio: "ignore" });
1437
+ installSpinner.stop("Dependencies installed!");
1438
+ } catch {
1439
+ installSpinner.stop('Failed to install dependencies. Run "bun install" manually.');
1440
+ }
1441
+ }
1425
1442
  if (initGit) {
1426
1443
  execSync("git init", { cwd: targetDir, stdio: "ignore" });
1427
1444
  execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
1428
1445
  execSync('git commit -m "init"', { cwd: targetDir, stdio: "ignore" });
1429
1446
  }
1430
- s.stop("Project created!");
1447
+ const steps = [`cd ${projectName}`];
1448
+ if (!installDeps) {
1449
+ steps.push("bun install");
1450
+ }
1451
+ const envPath = join2(targetDir, ".env.example");
1452
+ if (readFileSync2(envPath, "utf-8").trim().length > 0) {
1453
+ steps.push("cp .env.example .env # configure environment variables");
1454
+ }
1455
+ steps.push("bun dev");
1431
1456
  Gt(`Done! Next steps:
1432
1457
 
1433
- cd ${projectName}
1434
- bun install
1435
- bun dev`);
1458
+ ${steps.join(`
1459
+ `)}`);
1436
1460
  }
1437
1461
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rotor",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Scaffold Next.js projects with Bun, Tailwind, Biome, and more",
5
5
  "type": "module",
6
6
  "bin": "dist/index.js",
@@ -18,6 +18,10 @@
18
18
  "@types/node": "25.5.0",
19
19
  "typescript": "5.9.3"
20
20
  },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/yikZero/rotor"
24
+ },
21
25
  "keywords": ["create", "nextjs", "bun", "tailwind", "scaffold", "template"],
22
26
  "license": "MIT"
23
27
  }
@@ -1,4 +1,9 @@
1
- @import 'tailwindcss';
1
+ @import "tailwindcss";
2
+
3
+ @theme inline {
4
+ --font-sans: var(--font-geist-sans);
5
+ --font-mono: var(--font-geist-mono);
6
+ }
2
7
 
3
8
  /* [shadcn] */
4
9
  @theme inline {
@@ -1,6 +1,17 @@
1
1
  import type { Metadata } from 'next';
2
+ import { Geist, Geist_Mono } from 'next/font/google';
2
3
  import './globals.css';
3
4
 
5
+ const geistSans = Geist({
6
+ variable: '--font-geist-sans',
7
+ subsets: ['latin'],
8
+ });
9
+
10
+ const geistMono = Geist_Mono({
11
+ variable: '--font-geist-mono',
12
+ subsets: ['latin'],
13
+ });
14
+
4
15
  export const metadata: Metadata = {
5
16
  title: '{{PROJECT_NAME}}',
6
17
  description: '',
@@ -13,7 +24,11 @@ export default function RootLayout({
13
24
  }>) {
14
25
  return (
15
26
  <html lang="en">
16
- <body>{children}</body>
27
+ <body
28
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
+ >
30
+ {children}
31
+ </body>
17
32
  </html>
18
33
  );
19
34
  }
@@ -1,7 +1,7 @@
1
1
  export default function Home() {
2
2
  return (
3
3
  <main className="flex min-h-screen flex-col items-center justify-center p-24">
4
- <h1 className="text-4xl font-bold">{{PROJECT_NAME}}</h1>
4
+ <h1 className="font-bold text-4xl">Hello World</h1>
5
5
  </main>
6
6
  );
7
7
  }
@@ -4,6 +4,12 @@
4
4
  "indentStyle": "space",
5
5
  "indentWidth": 2
6
6
  },
7
+ "css": {
8
+ "parser": {
9
+ "cssModules": true,
10
+ "tailwindDirectives": true
11
+ }
12
+ },
7
13
  "javascript": {
8
14
  "formatter": {
9
15
  "quoteStyle": "single",
@@ -12,7 +18,47 @@
12
18
  },
13
19
  "linter": {
14
20
  "rules": {
15
- "recommended": true
21
+ "recommended": true,
22
+ "correctness": {
23
+ "noUnusedImports": "error",
24
+ "noUnusedFunctionParameters": "error"
25
+ },
26
+ "style": {
27
+ "useImportType": "error",
28
+ "useExportType": "error",
29
+ "useConsistentArrayType": "error",
30
+ "useForOf": "error",
31
+ "useNodejsImportProtocol": "error",
32
+ "noNonNullAssertion": "error",
33
+ "useFilenamingConvention": {
34
+ "level": "error",
35
+ "options": {
36
+ "filenameCases": ["kebab-case"]
37
+ }
38
+ }
39
+ },
40
+ "performance": {
41
+ "noBarrelFile": "error"
42
+ },
43
+ "suspicious": {
44
+ "noConsole": "warn",
45
+ "noExplicitAny": "error"
46
+ },
47
+ "nursery": {
48
+ "useSortedClasses": {
49
+ "level": "error",
50
+ "options": {
51
+ "functions": ["cn", "clsx", "cva"]
52
+ }
53
+ }
54
+ }
55
+ }
56
+ },
57
+ "assist": {
58
+ "actions": {
59
+ "source": {
60
+ "organizeImports": "on"
61
+ }
16
62
  }
17
63
  },
18
64
  "files": {
@@ -5,6 +5,6 @@ export default defineConfig({
5
5
  schema: './lib/schema.ts',
6
6
  dialect: 'postgresql',
7
7
  dbCredentials: {
8
- url: process.env.DATABASE_URL!,
8
+ url: process.env.DATABASE_URL ?? '',
9
9
  },
10
10
  });
@@ -1,7 +1,7 @@
1
1
  import { drizzle } from 'drizzle-orm/postgres-js';
2
2
  import postgres from 'postgres';
3
3
 
4
- const connectionString = process.env.DATABASE_URL!;
4
+ const connectionString = process.env.DATABASE_URL ?? '';
5
5
  const client = postgres(connectionString);
6
6
 
7
7
  export const db = drizzle({ client });
@@ -1,4 +1,4 @@
1
- import { clsx, type ClassValue } from 'clsx';
1
+ import { type ClassValue, clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
 
4
4
  export function cn(...inputs: ClassValue[]) {