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 +60 -0
- package/dist/index.js +38 -14
- package/package.json +5 -1
- package/template/app/globals.css +6 -1
- package/template/app/layout.tsx +16 -1
- package/template/app/page.tsx +1 -1
- package/template/biome.json +47 -1
- package/template/drizzle.config.ts +1 -1
- package/template/lib/db.ts +1 -1
- package/template/lib/utils.ts +1 -1
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(
|
|
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${
|
|
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
|
-
|
|
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
|
-
|
|
1434
|
-
|
|
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
|
+
"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
|
}
|
package/template/app/globals.css
CHANGED
package/template/app/layout.tsx
CHANGED
|
@@ -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
|
|
27
|
+
<body
|
|
28
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</body>
|
|
17
32
|
</html>
|
|
18
33
|
);
|
|
19
34
|
}
|
package/template/app/page.tsx
CHANGED
package/template/biome.json
CHANGED
|
@@ -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": {
|
package/template/lib/db.ts
CHANGED
|
@@ -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 });
|
package/template/lib/utils.ts
CHANGED