Initial commit
This commit is contained in:
547
skills/web-frameworks/scripts/nextjs_init.py
Normal file
547
skills/web-frameworks/scripts/nextjs_init.py
Normal file
@@ -0,0 +1,547 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Next.js Project Initialization Script
|
||||
|
||||
Initialize new Next.js project with best practices, TypeScript, and optimized configuration.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class NextJSInitializer:
|
||||
"""Initialize Next.js project with best practices."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
directory: Optional[Path] = None,
|
||||
typescript: bool = True,
|
||||
app_router: bool = True,
|
||||
src_dir: bool = False,
|
||||
tailwind: bool = False,
|
||||
eslint: bool = True,
|
||||
import_alias: str = "@/*",
|
||||
):
|
||||
"""
|
||||
Initialize NextJSInitializer.
|
||||
|
||||
Args:
|
||||
name: Project name
|
||||
directory: Target directory (default: current directory / name)
|
||||
typescript: Enable TypeScript
|
||||
app_router: Use App Router (recommended)
|
||||
src_dir: Use src/ directory
|
||||
tailwind: Include Tailwind CSS
|
||||
eslint: Include ESLint
|
||||
import_alias: Import alias pattern
|
||||
"""
|
||||
self.name = name
|
||||
self.directory = directory or Path.cwd() / name
|
||||
self.typescript = typescript
|
||||
self.app_router = app_router
|
||||
self.src_dir = src_dir
|
||||
self.tailwind = tailwind
|
||||
self.eslint = eslint
|
||||
self.import_alias = import_alias
|
||||
|
||||
def validate_name(self) -> None:
|
||||
"""Validate project name."""
|
||||
if not self.name:
|
||||
raise ValueError("Project name cannot be empty")
|
||||
|
||||
if not self.name.replace("-", "").replace("_", "").isalnum():
|
||||
raise ValueError(
|
||||
"Project name can only contain letters, numbers, hyphens, and underscores"
|
||||
)
|
||||
|
||||
if self.name[0].isdigit():
|
||||
raise ValueError("Project name cannot start with a number")
|
||||
|
||||
def check_directory(self) -> None:
|
||||
"""Check if target directory exists."""
|
||||
if self.directory.exists():
|
||||
raise FileExistsError(f"Directory '{self.directory}' already exists")
|
||||
|
||||
def create_directory_structure(self) -> None:
|
||||
"""Create project directory structure."""
|
||||
print(f"Creating directory structure in {self.directory}...")
|
||||
|
||||
# Create base directories
|
||||
self.directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Determine app/pages directory location
|
||||
base_dir = self.directory / "src" if self.src_dir else self.directory
|
||||
|
||||
if self.app_router:
|
||||
app_dir = base_dir / "app"
|
||||
app_dir.mkdir(parents=True, exist_ok=True)
|
||||
(app_dir / "favicon.ico").touch()
|
||||
self._create_app_router_files(app_dir)
|
||||
else:
|
||||
pages_dir = base_dir / "pages"
|
||||
pages_dir.mkdir(parents=True, exist_ok=True)
|
||||
self._create_pages_router_files(pages_dir)
|
||||
|
||||
# Create additional directories
|
||||
(self.directory / "public").mkdir(exist_ok=True)
|
||||
(base_dir / "components").mkdir(parents=True, exist_ok=True)
|
||||
(base_dir / "lib").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _create_app_router_files(self, app_dir: Path) -> None:
|
||||
"""Create App Router files."""
|
||||
ext = "tsx" if self.typescript else "jsx"
|
||||
|
||||
# Create layout
|
||||
layout_content = self._get_layout_content()
|
||||
(app_dir / f"layout.{ext}").write_text(layout_content)
|
||||
|
||||
# Create page
|
||||
page_content = self._get_page_content()
|
||||
(app_dir / f"page.{ext}").write_text(page_content)
|
||||
|
||||
# Create global styles
|
||||
if self.tailwind:
|
||||
globals_content = self._get_tailwind_globals()
|
||||
else:
|
||||
globals_content = self._get_basic_globals()
|
||||
(app_dir / "globals.css").write_text(globals_content)
|
||||
|
||||
def _create_pages_router_files(self, pages_dir: Path) -> None:
|
||||
"""Create Pages Router files."""
|
||||
ext = "tsx" if self.typescript else "jsx"
|
||||
|
||||
# Create _app
|
||||
app_content = self._get_app_content()
|
||||
(pages_dir / f"_app.{ext}").write_text(app_content)
|
||||
|
||||
# Create index
|
||||
index_content = self._get_index_content()
|
||||
(pages_dir / f"index.{ext}").write_text(index_content)
|
||||
|
||||
def create_config_files(self) -> None:
|
||||
"""Create configuration files."""
|
||||
print("Creating configuration files...")
|
||||
|
||||
# package.json
|
||||
package_json = self._get_package_json()
|
||||
(self.directory / "package.json").write_text(
|
||||
json.dumps(package_json, indent=2)
|
||||
)
|
||||
|
||||
# next.config.js
|
||||
next_config = self._get_next_config()
|
||||
(self.directory / "next.config.js").write_text(next_config)
|
||||
|
||||
# tsconfig.json
|
||||
if self.typescript:
|
||||
tsconfig = self._get_tsconfig()
|
||||
(self.directory / "tsconfig.json").write_text(
|
||||
json.dumps(tsconfig, indent=2)
|
||||
)
|
||||
|
||||
# .eslintrc.json
|
||||
if self.eslint:
|
||||
eslint_config = self._get_eslint_config()
|
||||
(self.directory / ".eslintrc.json").write_text(
|
||||
json.dumps(eslint_config, indent=2)
|
||||
)
|
||||
|
||||
# tailwind.config
|
||||
if self.tailwind:
|
||||
tailwind_config = self._get_tailwind_config()
|
||||
ext = "ts" if self.typescript else "js"
|
||||
(self.directory / f"tailwind.config.{ext}").write_text(tailwind_config)
|
||||
|
||||
postcss_config = self._get_postcss_config()
|
||||
(self.directory / "postcss.config.js").write_text(postcss_config)
|
||||
|
||||
# .gitignore
|
||||
gitignore = self._get_gitignore()
|
||||
(self.directory / ".gitignore").write_text(gitignore)
|
||||
|
||||
# README.md
|
||||
readme = self._get_readme()
|
||||
(self.directory / "README.md").write_text(readme)
|
||||
|
||||
def _get_package_json(self) -> dict:
|
||||
"""Generate package.json content."""
|
||||
dependencies = {
|
||||
"next": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
}
|
||||
|
||||
dev_dependencies = {}
|
||||
|
||||
if self.typescript:
|
||||
dev_dependencies.update(
|
||||
{
|
||||
"typescript": "^5.0.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
}
|
||||
)
|
||||
|
||||
if self.eslint:
|
||||
dev_dependencies["eslint"] = "^8.0.0"
|
||||
dev_dependencies["eslint-config-next"] = "latest"
|
||||
|
||||
if self.tailwind:
|
||||
dependencies["tailwindcss"] = "^3.3.0"
|
||||
dependencies["autoprefixer"] = "^10.0.0"
|
||||
dependencies["postcss"] = "^8.0.0"
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
"version": "0.1.0",
|
||||
"private": True,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint" if self.eslint else None,
|
||||
},
|
||||
"dependencies": dependencies,
|
||||
"devDependencies": dev_dependencies,
|
||||
}
|
||||
|
||||
def _get_layout_content(self) -> str:
|
||||
"""Generate layout.tsx content."""
|
||||
import_css = (
|
||||
"import './globals.css'\n" if not self.tailwind else "import './globals.css'\n"
|
||||
)
|
||||
|
||||
if self.typescript:
|
||||
return f"""{import_css}
|
||||
export const metadata = {{
|
||||
title: '{self.name}',
|
||||
description: 'Generated by Next.js',
|
||||
}}
|
||||
|
||||
export default function RootLayout({{
|
||||
children,
|
||||
}}: {{
|
||||
children: React.ReactNode
|
||||
}}) {{
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{{children}}</body>
|
||||
</html>
|
||||
)
|
||||
}}
|
||||
"""
|
||||
return f"""{import_css}
|
||||
export const metadata = {{
|
||||
title: '{self.name}',
|
||||
description: 'Generated by Next.js',
|
||||
}}
|
||||
|
||||
export default function RootLayout({{ children }}) {{
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{{children}}</body>
|
||||
</html>
|
||||
)
|
||||
}}
|
||||
"""
|
||||
|
||||
def _get_page_content(self) -> str:
|
||||
"""Generate page.tsx content."""
|
||||
return """export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Welcome to Next.js!</h1>
|
||||
<p>Get started by editing this page.</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
"""
|
||||
|
||||
def _get_next_config(self) -> str:
|
||||
"""Generate next.config.js content."""
|
||||
return """/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
// Add your image domains here
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
"""
|
||||
|
||||
def _get_tsconfig(self) -> dict:
|
||||
"""Generate tsconfig.json content."""
|
||||
return {
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": True,
|
||||
"skipLibCheck": True,
|
||||
"strict": True,
|
||||
"noEmit": True,
|
||||
"esModuleInterop": True,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": True,
|
||||
"isolatedModules": True,
|
||||
"jsx": "preserve",
|
||||
"incremental": True,
|
||||
"plugins": [{"name": "next"}],
|
||||
"paths": {self.import_alias: ["./*"]},
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
||||
|
||||
def _get_eslint_config(self) -> dict:
|
||||
"""Generate .eslintrc.json content."""
|
||||
return {"extends": "next/core-web-vitals"}
|
||||
|
||||
def _get_tailwind_config(self) -> str:
|
||||
"""Generate tailwind.config content."""
|
||||
if self.typescript:
|
||||
return """import type { Config } from 'tailwindcss'
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
export default config
|
||||
"""
|
||||
return """/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
"""
|
||||
|
||||
def _get_postcss_config(self) -> str:
|
||||
"""Generate postcss.config.js content."""
|
||||
return """module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
"""
|
||||
|
||||
def _get_tailwind_globals(self) -> str:
|
||||
"""Generate globals.css with Tailwind."""
|
||||
return """@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
"""
|
||||
|
||||
def _get_basic_globals(self) -> str:
|
||||
"""Generate basic globals.css."""
|
||||
return """* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
"""
|
||||
|
||||
def _get_gitignore(self) -> str:
|
||||
"""Generate .gitignore content."""
|
||||
return """# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
"""
|
||||
|
||||
def _get_readme(self) -> str:
|
||||
"""Generate README.md content."""
|
||||
return f"""# {self.name}
|
||||
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with next.js initialization script.
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# or
|
||||
yarn install
|
||||
# or
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Then, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new).
|
||||
|
||||
Check out the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
"""
|
||||
|
||||
def _get_app_content(self) -> str:
|
||||
"""Generate _app content for Pages Router."""
|
||||
return """export default function App({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
"""
|
||||
|
||||
def _get_index_content(self) -> str:
|
||||
"""Generate index content for Pages Router."""
|
||||
return """export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Welcome to Next.js!</h1>
|
||||
<p>Get started by editing this page.</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
"""
|
||||
|
||||
def initialize(self) -> None:
|
||||
"""Run full initialization process."""
|
||||
try:
|
||||
print(f"Initializing Next.js project: {self.name}")
|
||||
print(f"TypeScript: {self.typescript}")
|
||||
print(f"App Router: {self.app_router}")
|
||||
print(f"Tailwind CSS: {self.tailwind}")
|
||||
print(f"ESLint: {self.eslint}")
|
||||
print()
|
||||
|
||||
self.validate_name()
|
||||
self.check_directory()
|
||||
self.create_directory_structure()
|
||||
self.create_config_files()
|
||||
|
||||
print()
|
||||
print(f"✓ Project initialized successfully!")
|
||||
print()
|
||||
print(f"Next steps:")
|
||||
print(f" cd {self.name}")
|
||||
print(f" npm install")
|
||||
print(f" npm run dev")
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Initialize Next.js project with best practices"
|
||||
)
|
||||
parser.add_argument("name", help="Project name")
|
||||
parser.add_argument(
|
||||
"--directory", type=Path, help="Target directory (default: ./<name>)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-typescript", action="store_true", help="Disable TypeScript"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pages-router", action="store_true", help="Use Pages Router instead of App Router"
|
||||
)
|
||||
parser.add_argument("--src-dir", action="store_true", help="Use src/ directory")
|
||||
parser.add_argument("--tailwind", action="store_true", help="Include Tailwind CSS")
|
||||
parser.add_argument("--no-eslint", action="store_true", help="Disable ESLint")
|
||||
parser.add_argument(
|
||||
"--import-alias", default="@/*", help="Import alias pattern (default: @/*)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
initializer = NextJSInitializer(
|
||||
name=args.name,
|
||||
directory=args.directory,
|
||||
typescript=not args.no_typescript,
|
||||
app_router=not args.pages_router,
|
||||
src_dir=args.src_dir,
|
||||
tailwind=args.tailwind,
|
||||
eslint=not args.no_eslint,
|
||||
import_alias=args.import_alias,
|
||||
)
|
||||
|
||||
initializer.initialize()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user