create-genesis-app 0.1.0

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,47 @@
1
+ # create-genesis-app
2
+
3
+ Scaffold an opinionated Next.js app into the current folder.
4
+
5
+ ```bash
6
+ pnpm create genesis-app . # into the current directory
7
+ pnpm create genesis-app my-app # into ./my-app
8
+ ```
9
+
10
+ Also works with `npm create genesis-app@latest .` and `yarn create genesis-app .`.
11
+
12
+ ## What you get
13
+
14
+ The default `create-next-app` stack, kept current:
15
+
16
+ - **Next.js** (App Router) + **React** latest
17
+ - **TypeScript** (strict)
18
+ - **Tailwind CSS v4**
19
+ - **ESLint** (flat config, `next/core-web-vitals` + `next/typescript`)
20
+
21
+ All template dependencies are set to `latest`, so every scaffold pulls the newest
22
+ published versions at install time.
23
+
24
+ ## Flags
25
+
26
+ | Flag | Effect |
27
+ | --------------------------------------- | -------------------------------------- |
28
+ | `--no-install` | Copy files but skip dependency install |
29
+ | `--no-git` | Skip `git init` |
30
+ | `--pnpm` / `--npm` / `--yarn` / `--bun` | Force a specific package manager |
31
+
32
+ By default the CLI uses the package manager that invoked it (`pnpm create` → pnpm,
33
+ `npm create` → npm, etc.), and falls back to **pnpm** when it can't tell. Pass one of
34
+ the flags above to override.
35
+
36
+ ## Repo layout
37
+
38
+ ```
39
+ create-genesis-app/
40
+ ├── package.json # the published package (bin + files)
41
+ ├── bin/index.js # the scaffolder (Node built-ins only, no deps)
42
+ └── template/ # the project that gets copied
43
+ ├── _gitignore # de-dotted: npm strips real dotfiles on publish
44
+ ├── src/
45
+ ├── public/
46
+ └── package.json # deps set to "latest"
47
+ ```
package/bin/index.js ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { cp, rename, readFile, writeFile, readdir, rm } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join, resolve, basename } from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const templateDir = join(__dirname, "..", "template");
10
+
11
+ // ---- parse args -------------------------------------------------------------
12
+ const argv = process.argv.slice(2);
13
+ const flags = new Set(argv.filter((a) => a.startsWith("--")));
14
+ const positional = argv.filter((a) => !a.startsWith("--"));
15
+
16
+ const target = resolve(process.cwd(), positional[0] ?? ".");
17
+ const projectName = basename(target);
18
+ const skipInstall = flags.has("--no-install");
19
+ const skipGit = flags.has("--no-git");
20
+
21
+ // Files that npm strips on publish, stored de-dotted in template/.
22
+ const dotfiles = { _gitignore: ".gitignore", _npmrc: ".npmrc", _env: ".env", "_env.local": ".env.local" };
23
+
24
+ // Pick the package manager: explicit flag > the PM that invoked us > pnpm.
25
+ const KNOWN_PMS = ["pnpm", "npm", "yarn", "bun"];
26
+ const pmFlag = KNOWN_PMS.find((p) => flags.has(`--${p}`));
27
+ const invokedWith = (process.env.npm_config_user_agent ?? "").split("/")[0];
28
+ const pm = pmFlag ?? (KNOWN_PMS.includes(invokedWith) ? invokedWith : "pnpm");
29
+
30
+ // ---- scaffold ---------------------------------------------------------------
31
+ console.log(`\nCreating a Next.js app in ${target}\n`);
32
+
33
+ await cp(templateDir, target, {
34
+ recursive: true,
35
+ filter: (src) =>
36
+ !/[\\/](node_modules|\.git|pnpm-lock\.yaml|package-lock\.json)$/.test(src),
37
+ });
38
+
39
+ // Restore the de-dotted files to their real names.
40
+ for (const [from, to] of Object.entries(dotfiles)) {
41
+ const fromPath = join(target, from);
42
+ if (existsSync(fromPath)) await rename(fromPath, join(target, to));
43
+ }
44
+
45
+ // Drop keep-files: they only exist so npm ships otherwise-empty folders.
46
+ // Removing them leaves the folder present but genuinely empty in the scaffold.
47
+ const allEntries = await readdir(target, { recursive: true });
48
+ for (const entry of allEntries) {
49
+ if ([".gitkeep", ".keep"].includes(basename(entry))) {
50
+ await rm(join(target, entry));
51
+ }
52
+ }
53
+
54
+ // Use the target folder name as the project name.
55
+ const pkgPath = join(target, "package.json");
56
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
57
+ pkg.name = projectName;
58
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
59
+
60
+ // Substitute {{name}} placeholders (e.g. in the README and metadata).
61
+ for (const file of ["README.md", "src/app/layout.tsx"]) {
62
+ const filePath = join(target, file);
63
+ if (!existsSync(filePath)) continue;
64
+ const contents = await readFile(filePath, "utf8");
65
+ await writeFile(filePath, contents.replaceAll("{{name}}", projectName));
66
+ }
67
+
68
+ // ---- install ----------------------------------------------------------------
69
+ // Done before git init on purpose: with no repo yet, editors can't transiently
70
+ // flag the thousands of node_modules files written during install.
71
+ if (!skipInstall) {
72
+ console.log(`Installing dependencies with ${pm}...\n`);
73
+ spawnSync(pm, ["install"], { cwd: target, stdio: "inherit" });
74
+ }
75
+
76
+ // ---- git --------------------------------------------------------------------
77
+ // Init + initial commit last, so the working tree lands clean (.gitignore is
78
+ // already in place, so node_modules is excluded from the commit).
79
+ if (!skipGit && !existsSync(join(target, ".git"))) {
80
+ const git = (...args) =>
81
+ spawnSync("git", args, { cwd: target, stdio: "ignore" });
82
+ git("init", "-q");
83
+ git("add", "-A");
84
+ const msg = "init";
85
+ const commit = git("commit", "-m", msg, "--quiet");
86
+ // Fall back to a neutral identity if the user has none configured globally.
87
+ if (commit.status !== 0) {
88
+ git(
89
+ "-c",
90
+ "user.name=create-genesis",
91
+ "-c",
92
+ "user.email=create-genesis@users.noreply.github.com",
93
+ "commit",
94
+ "-m",
95
+ msg,
96
+ "--quiet",
97
+ );
98
+ }
99
+ }
100
+
101
+ // ---- done -------------------------------------------------------------------
102
+ const rel = target === process.cwd() ? "." : projectName;
103
+ console.log(`\n✓ Done.\n`);
104
+ if (rel !== ".") console.log(` cd ${rel}`);
105
+ if (skipInstall) console.log(` ${pm} install`);
106
+ console.log(` ${pm} run dev\n`);
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "create-genesis-app",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a Next.js app (App Router, TypeScript, Tailwind v4) into the current folder.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-genesis-app": "bin/index.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "template"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "keywords": [
17
+ "next",
18
+ "nextjs",
19
+ "react",
20
+ "tailwind",
21
+ "typescript",
22
+ "create",
23
+ "template",
24
+ "starter"
25
+ ],
26
+ "license": "MIT"
27
+ }
File without changes
@@ -0,0 +1,5 @@
1
+ <!-- BEGIN:nextjs-agent-rules -->
2
+ # This is NOT the Next.js you know
3
+
4
+ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
5
+ <!-- END:nextjs-agent-rules -->
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,9 @@
1
+ # {{name}}
2
+
3
+ ## Getting started
4
+
5
+ ```bash
6
+ pnpm dev
7
+ ```
8
+
9
+ Then open [http://localhost:3000](http://localhost:3000) and edit `src/app/page.tsx`.
@@ -0,0 +1,41 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ devIndicators: false,
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "genesis-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "next": "latest",
13
+ "react": "latest",
14
+ "react-dom": "latest",
15
+ "clsx": "latest",
16
+ "tailwind-merge": "latest",
17
+ "radix-ui": "latest",
18
+ "class-variance-authority": "latest"
19
+ },
20
+ "devDependencies": {
21
+ "@tailwindcss/postcss": "latest",
22
+ "@types/node": "latest",
23
+ "@types/react": "latest",
24
+ "@types/react-dom": "latest",
25
+ "eslint": "latest",
26
+ "eslint-config-next": "latest",
27
+ "tailwindcss": "latest",
28
+ "typescript": "latest"
29
+ }
30
+ }
@@ -0,0 +1,7 @@
1
+ allowBuilds:
2
+ esbuild: true
3
+ sharp: true
4
+ unrs-resolver: true
5
+ ignoredBuiltDependencies:
6
+ - sharp
7
+ - unrs-resolver
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
File without changes
Binary file
@@ -0,0 +1,10 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme inline {
4
+ --font-sans: var(--font-regular-sans);
5
+ --font-mono: var(--font-regular-mono);
6
+ }
7
+
8
+ body {
9
+ font-family: var(--font-regular-sans), Arial, Helvetica, sans-serif;
10
+ }
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+ import { cn } from "@/lib/tailwind-utils";
4
+ import fonts from "@/lib/fonts";
5
+
6
+ export const metadata: Metadata = {
7
+ title: "{{name}}",
8
+ description: "{{name}} - created with create-genesis",
9
+ };
10
+
11
+ export default function RootLayout({
12
+ children,
13
+ }: Readonly<{
14
+ children: React.ReactNode;
15
+ }>) {
16
+ return (
17
+ <html lang="en" className={cn(fonts, "h-full antialiased")}>
18
+ <body className="min-h-full flex flex-col">{children}</body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,11 @@
1
+ interface PageProps {}
2
+
3
+ const Page = ({}: PageProps) => {
4
+ return (
5
+ <div className="">
6
+ <h1>Hello World!</h1>
7
+ </div>
8
+ );
9
+ };
10
+
11
+ export default Page;
File without changes
@@ -0,0 +1,17 @@
1
+ import { Inter, Geist_Mono } from "next/font/google";
2
+ import { cn } from "@/lib/tailwind-utils";
3
+
4
+ const regularSansFont = Inter({
5
+ variable: "--font-regular-sans",
6
+ subsets: ["latin"],
7
+ });
8
+
9
+ const regularMonoFont = Geist_Mono({
10
+ variable: "--font-regular-mono",
11
+ subsets: ["latin"],
12
+ });
13
+
14
+ export default cn(
15
+ regularSansFont.variable,
16
+ regularMonoFont.variable,
17
+ );
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }