rnt-next
Version:
CLI para criar aplicações Next.js com Styled Components ou Tailwind CSS, Turbopack e configurações pré-definidas - Criado por RNT
1,346 lines (1,177 loc) • 33.3 kB
JavaScript
#!/usr/bin/env node
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const readline = require("readline");
const appName = process.argv[2] || "novo-app";
const appPath = path.join(process.cwd(), appName);
const execCommand = (cmd) => {
try {
execSync(cmd, { stdio: "inherit" });
} catch (error) {
console.error(`❌ Erro ao executar: ${cmd}`);
process.exit(1);
}
};
// Função utilitária para perguntar no terminal
function askQuestion(query) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) =>
rl.question(query, (ans) => {
rl.close();
resolve(ans);
})
);
}
async function main() {
console.log("🎨 RNT Next CLI - Criado por RNT");
console.log("=====================================");
// Pergunta sobre a escolha do CSS
const cssChoice = await askQuestion(
"Qual biblioteca de CSS você deseja usar?\n1. Styled Components\n2. Tailwind CSS\nEscolha (1 ou 2): "
);
const useStyledComponents = cssChoice === "1";
const useTailwind = cssChoice === "2";
if (!useStyledComponents && !useTailwind) {
console.log("❌ Escolha inválida. Usando Tailwind CSS por padrão.");
}
const finalChoice = useStyledComponents ? "styled-components" : "tailwind";
console.log(
`✅ Configurando projeto com ${
finalChoice === "styled-components" ? "Styled Components" : "Tailwind CSS"
}`
);
// 🚀 Criando um novo projeto com Next.js e TypeScript
console.log("📦 Criando novo projeto com Next.js...");
if (finalChoice === "styled-components") {
// Criar projeto sem Tailwind para Styled Components
execCommand(
`npx --yes create-next-app@latest ${appName} --typescript --no-tailwind --eslint --app --src-dir --import-alias "@/*" --turbo`
);
} else {
// Criar projeto com Tailwind
execCommand(
`npx --yes create-next-app@latest ${appName} --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --turbo`
);
}
// Verificar se o diretório foi criado antes de mudar para ele
if (!fs.existsSync(appPath)) {
console.error(`❌ Erro: Diretório ${appPath} não foi criado`);
process.exit(1);
}
process.chdir(appPath);
// 🎯 Instalando dependências baseadas na escolha
console.log("📦 Instalando Dependências de produção...");
if (finalChoice === "styled-components") {
execCommand(
"npm install styled-components @types/styled-components react-redux @reduxjs/toolkit framer-motion react-icons immer redux@latest clsx class-variance-authority lucide-react --save"
);
} else {
execCommand(
"npm install react-redux @reduxjs/toolkit framer-motion react-icons immer redux@latest clsx class-variance-authority lucide-react --save"
);
}
console.log("📦 Instalando Dependências de desenvolvimento...");
if (finalChoice === "styled-components") {
execCommand(
"npm install @types/styled-components eslint-plugin-prettier prettier eslint-config-prettier --save-dev"
);
} else {
execCommand(
"npm install eslint-plugin-prettier prettier eslint-config-prettier --save-dev"
);
}
// 🏗 Criando estrutura de pastas
console.log("📂 Criando estrutura de pastas...");
const folders = [
"src/styles",
"src/components/ui",
"src/components/layout",
"src/components/layout/header",
"src/components/layout/footer",
"src/lib",
"src/hooks",
"src/utils",
"src/redux",
"src/redux/slices",
"src/types",
".vscode",
];
folders.forEach((folder) =>
fs.mkdirSync(path.join(appPath, folder), { recursive: true })
);
// 📝 Criando arquivos de configuração...
console.log("📄 Criando arquivos de configuração...");
// VSCode settings
fs.writeFileSync(
".vscode/settings.json",
JSON.stringify(
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll": true,
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features",
},
"typescript.tsdk": "node_modules/typescript/lib",
},
null,
2
)
);
// Prettierrc settings
fs.writeFileSync(
".prettierrc",
JSON.stringify(
{
trailingComma: "none",
semi: false,
singleQuote: true,
printWidth: 150,
arrowParens: "avoid",
},
null,
2
)
);
// Editorconfig settings
fs.writeFileSync(
".editorconfig",
`root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
`
);
// Next.js config
if (finalChoice === "styled-components") {
fs.writeFileSync(
"next.config.js",
`/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
},
compiler: {
styledComponents: true,
},
images: {
domains: ['placehold.co'],
},
}
module.exports = nextConfig
`
);
} else {
fs.writeFileSync(
"next.config.js",
`/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
},
images: {
domains: ['placehold.co'],
},
}
module.exports = nextConfig
`
);
}
// Theme configuration
fs.writeFileSync(
"src/styles/theme.ts",
`export const theme = {
breakpoints: {
sm: '480px',
md: '768px',
lg: '1024px',
xl: '1200px'
},
colors: {
primaryColor: '#011627',
secondaryColor: '#023864',
textColor: '#fff',
yellow: '#ffff00',
yellow2: '#E1A32A',
blue: '#0000FF',
blue2: '#1E90FF',
gray: '#666666',
gray2: '#a1a1a1',
orange: '#ff4500',
orange2: '#ff7f50',
black: '#000',
red: '#FF0000',
green: '#008000',
green2: '#44BD32',
}
}
export const darkTheme = {
colors: {
primaryColor: '#13161b',
secondaryColor: '#1c1f25',
background: '#2F2F2F',
inputColor: '#0d0e12',
white: '#121212',
blue: '#0d6efd',
blue2: '#0000FF',
red: '#FF3347',
green: '#28a745',
orange: '#ff4500',
yellow: '#fffF00',
shadow: '#000',
grey: '#a1a1a1',
textColor: '#f1f1f1',
neon: {
pink1: '#FF1493',
pink2: '#FF00FF',
green1: '#39FF14',
green2: '#00FF7F',
blue1: '#00BFFF',
blue2: '#00FFFF'
}
}
}
export const lightTheme = {
colors: {
primaryColor: '#666666',
secondaryColor: '#a1a1a1',
background: '#808080',
inputColor: '#f1f1f1',
white: '#ffffff',
blue: '#3a86ff',
blue2: '#0000FF',
red: '#FF0000',
green: '#34d399',
orange: '#ff4500',
yellow: '#ffff00',
shadow: '#000',
grey: '#a1a1a1',
textColor: '#13161b',
neon: {
pink1: '#FF1493',
pink2: '#FF00FF',
green1: '#39FF14',
green2: '#00FF7F',
blue1: '#00FFFF',
blue2: '#00BFFF'
}
}
}
export const themeConfig = {
light: lightTheme,
dark: darkTheme
}
`
);
// Criar arquivos específicos baseados na escolha
if (finalChoice === "styled-components") {
await createStyledComponentsFiles();
} else {
await createTailwindFiles();
}
// Providers
fs.writeFileSync(
"src/components/providers.tsx",
`'use client'
import { Provider } from 'react-redux'
import { store } from '@/redux/store'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<Provider store={store}>
{children}
</Provider>
)
}`
);
// Redux Store
fs.writeFileSync(
"src/redux/store.ts",
`import { combineReducers, configureStore as toolkitConfigureStore } from '@reduxjs/toolkit'
// Import your slices here
// import counterSlice from './slices/counterSlice'
const rootReducer = combineReducers({
// Add your reducers here
// counter: counterSlice,
})
export type RootState = ReturnType<typeof rootReducer>
export function configureStore(preloadedState?: Partial<RootState>) {
return toolkitConfigureStore({
reducer: rootReducer,
preloadedState,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST'],
},
}),
})
}
export const store = configureStore()
export type AppStore = ReturnType<typeof configureStore>
export type AppDispatch = AppStore['dispatch']
export type RootReducer = typeof rootReducer`
);
// Criar página inicial personalizada
await createCustomHomePage(finalChoice);
// Criar layout personalizado
await createCustomLayout(finalChoice);
console.log("✅ Projeto criado com sucesso!");
console.log(`📁 Navegue para: cd ${appName}`);
console.log("🚀 Execute: npm run dev");
console.log(
`🎨 Configurado com: ${
finalChoice === "styled-components" ? "Styled Components" : "Tailwind CSS"
}`
);
console.log("💙 Criado por RNT");
}
async function createStyledComponentsFiles() {
// Global Styles para Styled Components
fs.writeFileSync(
"src/styles/globalStyles.tsx",
`'use client'
import { createGlobalStyle } from 'styled-components';
import { theme } from './theme';
export const GlobalStyles = createGlobalStyle\`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: \${theme.colors.primaryColor};
color: \${theme.colors.textColor};
font-family: 'Inter', sans-serif;
}
a {
text-decoration: none;
color: inherit;
}
html {
scroll-behavior: smooth;
}
ul, ol {
list-style: none;
}
button {
border: none;
background: none;
cursor: pointer;
}
input, textarea {
outline: none;
}
\`;
`
);
// Styled Components Registry
fs.writeFileSync(
"src/lib/styled-components-registry.tsx",
`'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}`
);
// Home Styles para Styled Components
fs.writeFileSync(
"src/styles/HomeStyles.ts",
`'use client'
import styled from 'styled-components'
import { theme } from './theme'
export const MainContainer = styled.div\`
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: \${theme.colors.primaryColor};
\`
export const ContentSection = styled.main\`
flex: 1;
padding: 60px 20px;
max-width: 1200px;
margin: 0 auto;
width: 100%;
\`
export const HeroSection = styled.section\`
text-align: center;
margin-bottom: 80px;
h1 {
font-size: 3.5rem;
font-weight: 700;
color: \${theme.colors.textColor};
margin-bottom: 20px;
background: linear-gradient(135deg, \${theme.colors.blue2}, \${theme.colors.yellow2});
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
p {
font-size: 1.25rem;
color: \${theme.colors.gray2};
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
@media (max-width: \${theme.breakpoints.md}) {
margin-bottom: 60px;
h1 {
font-size: 2.5rem;
}
p {
font-size: 1.1rem;
}
}
@media (max-width: \${theme.breakpoints.sm}) {
h1 {
font-size: 2rem;
}
p {
font-size: 1rem;
}
}
\`
export const FeatureGrid = styled.div\`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-top: 40px;
@media (max-width: \${theme.breakpoints.md}) {
grid-template-columns: 1fr;
gap: 20px;
}
\`
export const FeatureCard = styled.div\`
background: rgba(255, 255, 255, 0.05);
padding: 40px 30px;
border-radius: 12px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.08);
border-color: \${theme.colors.blue2};
}
.icon {
font-size: 3rem;
color: \${theme.colors.blue2};
margin-bottom: 20px;
}
h3 {
font-size: 1.5rem;
color: \${theme.colors.textColor};
margin-bottom: 15px;
}
p {
color: \${theme.colors.gray2};
line-height: 1.6;
}
@media (max-width: \${theme.breakpoints.md}) {
padding: 30px 20px;
.icon {
font-size: 2.5rem;
}
h3 {
font-size: 1.3rem;
}
}
\`
export const CTASection = styled.section\`
text-align: center;
margin-top: 80px;
padding: 60px 20px;
background: linear-gradient(135deg, rgba(30, 144, 255, 0.1), rgba(225, 163, 42, 0.1));
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
h2 {
font-size: 2.5rem;
color: \${theme.colors.textColor};
margin-bottom: 20px;
}
p {
font-size: 1.1rem;
color: \${theme.colors.gray2};
margin-bottom: 30px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
@media (max-width: \${theme.breakpoints.md}) {
margin-top: 60px;
padding: 40px 20px;
h2 {
font-size: 2rem;
}
p {
font-size: 1rem;
}
}
\`
export const Button = styled.button\`
background: linear-gradient(135deg, \${theme.colors.blue2}, \${theme.colors.yellow2});
color: white;
padding: 15px 30px;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(30, 144, 255, 0.3);
}
&:active {
transform: translateY(0);
}
@media (max-width: \${theme.breakpoints.md}) {
padding: 12px 25px;
font-size: 1rem;
}
\`
`
);
// Header Styles para Styled Components
fs.writeFileSync(
"src/components/layout/header/HeaderStyles.ts",
`'use client'
import styled from 'styled-components'
import { theme } from '@/styles/theme'
export const HeaderContainer = styled.header\`
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
background-color: \${theme.colors.primaryColor};
border-bottom: 1px solid \${theme.colors.secondaryColor};
position: sticky;
top: 0;
z-index: 100;
@media (max-width: \${theme.breakpoints.md}) {
flex-direction: column;
gap: 15px;
}
\`
export const Logo = styled.div\`
display: flex;
align-items: center;
h1 {
color: \${theme.colors.textColor};
font-size: 1.8rem;
font-weight: 700;
background: linear-gradient(135deg, \${theme.colors.blue2}, \${theme.colors.yellow2});
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
\`
export const NavMenu = styled.nav\`
display: flex;
gap: 30px;
@media (max-width: \${theme.breakpoints.md}) {
gap: 20px;
flex-wrap: wrap;
justify-content: center;
}
\`
export const NavItem = styled.div\`
a {
text-decoration: none;
color: \${theme.colors.textColor};
font-weight: 500;
font-size: 16px;
transition: color 0.3s ease;
position: relative;
&:hover {
color: \${theme.colors.blue2};
}
&::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -5px;
left: 0;
background-color: \${theme.colors.blue2};
transition: width 0.3s ease;
}
&:hover::after {
width: 100%;
}
}
\`
`
);
// Footer Styles para Styled Components
fs.writeFileSync(
"src/components/layout/footer/FooterStyles.ts",
`'use client'
import styled from 'styled-components'
import { theme } from '@/styles/theme'
export const FooterContainer = styled.footer\`
background-color: \${theme.colors.primaryColor};
border-top: 1px solid \${theme.colors.secondaryColor};
padding: 40px 20px 20px;
margin-top: auto;
\`
export const FooterContent = styled.div\`
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
text-align: center;
p {
color: \${theme.colors.textColor};
font-size: 14px;
opacity: 0.8;
}
@media (max-width: \${theme.breakpoints.md}) {
gap: 15px;
p {
font-size: 12px;
}
}
\`
export const SocialLinks = styled.div\`
display: flex;
gap: 20px;
align-items: center;
a {
color: \${theme.colors.textColor};
transition: all 0.3s ease;
padding: 8px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: \${theme.colors.blue2};
background-color: rgba(30, 144, 255, 0.1);
transform: translateY(-2px);
}
}
@media (max-width: \${theme.breakpoints.md}) {
gap: 15px;
a {
padding: 6px;
svg {
width: 20px;
height: 20px;
}
}
}
\`
`
);
}
async function createTailwindFiles() {
// Global styles para Tailwind
fs.writeFileSync(
"src/styles/globals.css",
`@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
list-style: none;
}
body {
font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #011627;
color: #fff;
transition: background-color 0.3s, color 0.3s;
}
html {
scroll-behavior: smooth;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@layer components {
.gradient-text {
@apply bg-gradient-to-r from-blue-500 to-yellow-400 bg-clip-text text-transparent;
}
.feature-card {
@apply bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-8 text-center transition-all duration-300 hover:bg-white/8 hover:border-blue-500 hover:-translate-y-1;
}
.cta-button {
@apply bg-gradient-to-r from-blue-500 to-yellow-400 text-white px-8 py-4 rounded-lg font-semibold text-lg uppercase tracking-wide transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-blue-500/30;
}
}
`
);
}
async function createCustomHomePage(cssChoice) {
if (cssChoice === "styled-components") {
fs.writeFileSync(
"src/app/page.tsx",
`'use client'
import { FaReact, FaCode, FaRocket } from 'react-icons/fa'
import {
MainContainer,
ContentSection,
HeroSection,
FeatureGrid,
FeatureCard,
CTASection,
Button
} from '@/styles/HomeStyles'
export default function Home() {
return (
<MainContainer>
<ContentSection>
<HeroSection>
<h1>RNT Next CLI</h1>
<p>
Um CLI poderoso para criar aplicações Next.js com configurações pré-definidas e estrutura otimizada.
Criado por RNT para acelerar o desenvolvimento de projetos modernos.
</p>
</HeroSection>
<FeatureGrid>
<FeatureCard>
<div className="icon">
<FaReact />
</div>
<h3>Next.js 15+</h3>
<p>
Versão mais recente do Next.js com App Router, Turbopack e todas as funcionalidades modernas.
</p>
</FeatureCard>
<FeatureCard>
<div className="icon">
<FaCode />
</div>
<h3>Styled Components</h3>
<p>
CSS-in-JS com Styled Components, temas personalizáveis e componentes reutilizáveis.
</p>
</FeatureCard>
<FeatureCard>
<div className="icon">
<FaRocket />
</div>
<h3>Pronto para Produção</h3>
<p>
ESLint, Prettier, TypeScript e Redux Toolkit configurados para máxima produtividade.
</p>
</FeatureCard>
</FeatureGrid>
<CTASection>
<h2>Comece Agora</h2>
<p>
Execute o comando abaixo e tenha um projeto Next.js completo em segundos.
</p>
<Button onClick={() => navigator.clipboard.writeText('npx rnt-next meu-projeto')}>
Copiar Comando
</Button>
</CTASection>
</ContentSection>
</MainContainer>
)
}
`
);
} else {
fs.writeFileSync(
"src/app/page.tsx",
`'use client'
import { FaReact, FaCode, FaRocket } from 'react-icons/fa'
export default function Home() {
const copyCommand = () => {
navigator.clipboard.writeText('npx rnt-next meu-projeto')
}
return (
<div className="min-h-screen flex flex-col bg-[#011627]">
<main className="flex-1 px-5 py-15 max-w-6xl mx-auto w-full">
<section className="text-center mb-20">
<h1 className="text-5xl md:text-6xl font-bold text-white mb-5 gradient-text">
RNT Next CLI
</h1>
<p className="text-xl text-gray-400 max-w-2xl mx-auto leading-relaxed">
Um CLI poderoso para criar aplicações Next.js com configurações pré-definidas e estrutura otimizada.
Criado por RNT para acelerar o desenvolvimento de projetos modernos.
</p>
</section>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-10">
<div className="feature-card">
<div className="text-5xl text-blue-500 mb-5">
<FaReact />
</div>
<h3 className="text-2xl text-white mb-4">Next.js 15+</h3>
<p className="text-gray-400 leading-relaxed">
Versão mais recente do Next.js com App Router, Turbopack e todas as funcionalidades modernas.
</p>
</div>
<div className="feature-card">
<div className="text-5xl text-blue-500 mb-5">
<FaCode />
</div>
<h3 className="text-2xl text-white mb-4">Tailwind CSS</h3>
<p className="text-gray-400 leading-relaxed">
Framework CSS utilitário para desenvolvimento rápido e responsivo com design moderno.
</p>
</div>
<div className="feature-card">
<div className="text-5xl text-blue-500 mb-5">
<FaRocket />
</div>
<h3 className="text-2xl text-white mb-4">Pronto para Produção</h3>
<p className="text-gray-400 leading-relaxed">
ESLint, Prettier, TypeScript e Redux Toolkit configurados para máxima produtividade.
</p>
</div>
</div>
<section className="text-center mt-20 p-15 bg-gradient-to-r from-blue-500/10 to-yellow-400/10 rounded-2xl border border-white/10">
<h2 className="text-4xl text-white mb-5">Comece Agora</h2>
<p className="text-xl text-gray-400 mb-8 max-w-lg mx-auto">
Execute o comando abaixo e tenha um projeto Next.js completo em segundos.
</p>
<button onClick={copyCommand} className="cta-button">
Copiar Comando
</button>
</section>
</main>
</div>
)
}
`
);
}
}
async function createCustomLayout(cssChoice) {
if (cssChoice === "styled-components") {
// Layout com Styled Components
fs.writeFileSync(
"src/app/layout.tsx",
`import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import StyledComponentsRegistry from '@/lib/styled-components-registry'
import { GlobalStyles } from '@/styles/globalStyles'
import { Providers } from '@/components/providers'
import Header from '@/components/layout/header/Header'
import Footer from '@/components/layout/footer/Footer'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'RNT Next CLI - Criado por RNT',
description: 'CLI para criar aplicações Next.js com configurações pré-definidas',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="pt-BR">
<body className={inter.className}>
<StyledComponentsRegistry>
<GlobalStyles />
<Providers>
<Header />
{children}
<Footer />
</Providers>
</StyledComponentsRegistry>
</body>
</html>
)
}
`
);
// Header com Styled Components
fs.writeFileSync(
"src/components/layout/header/Header.tsx",
`'use client'
import Link from 'next/link'
import { HeaderContainer, Logo, NavMenu, NavItem } from './HeaderStyles'
const Header = () => {
return (
<HeaderContainer>
<Logo>
<h1>RNT</h1>
</Logo>
<NavMenu>
<NavItem>
<Link href="/">Home</Link>
</NavItem>
<NavItem>
<Link href="/docs">Docs</Link>
</NavItem>
<NavItem>
<Link href="/examples">Examples</Link>
</NavItem>
<NavItem>
<Link href="/about">About</Link>
</NavItem>
</NavMenu>
</HeaderContainer>
)
}
export default Header`
);
// Footer com Styled Components
fs.writeFileSync(
"src/components/layout/footer/Footer.tsx",
`'use client'
import { FaGithub, FaInstagram, FaLinkedin } from 'react-icons/fa'
import { FooterContainer, SocialLinks, FooterContent } from './FooterStyles'
const getCurrentYear = () => {
const date = new Date()
return date.getFullYear()
}
const Footer = () => {
return (
<FooterContainer>
<FooterContent>
<p>© {getCurrentYear()} RNT Projects. Todos os direitos reservados.</p>
<SocialLinks className="social-links">
<a href="https://github.com" target="_blank" rel="noopener noreferrer" aria-label="GitHub">
<FaGithub size={24} />
</a>
<a href="https://linkedin.com" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn">
<FaLinkedin size={24} />
</a>
<a href="https://instagram.com" target="_blank" rel="noopener noreferrer" aria-label="Instagram">
<FaInstagram size={24} />
</a>
</SocialLinks>
</FooterContent>
</FooterContainer>
)
}
export default Footer`
);
} else {
// Layout com Tailwind
fs.writeFileSync(
"src/app/layout.tsx",
`import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { Providers } from '@/components/providers'
import Header from '@/components/layout/header/Header'
import Footer from '@/components/layout/footer/Footer'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'RNT Next CLI - Criado por RNT',
description: 'CLI para criar aplicações Next.js com configurações pré-definidas',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="pt-BR">
<body className={inter.className}>
<Providers>
<Header />
{children}
<Footer />
</Providers>
</body>
</html>
)
}
`
);
// Header com Tailwind
fs.writeFileSync(
"src/components/layout/header/Header.tsx",
`'use client'
import Link from 'next/link'
const Header = () => {
return (
<header className="flex items-center justify-between p-5 bg-[#011627] border-b border-[#023864] sticky top-0 z-50">
<div className="flex items-center">
<h1 className="text-white text-3xl font-bold gradient-text">RNT</h1>
</div>
<nav className="flex gap-8">
<Link
href="/"
className="text-white font-medium text-base transition-colors duration-300 hover:text-blue-500 relative group"
>
Home
<span className="absolute bottom-[-5px] left-0 w-0 h-0.5 bg-blue-500 transition-all duration-300 group-hover:w-full"></span>
</Link>
<Link
href="/docs"
className="text-white font-medium text-base transition-colors duration-300 hover:text-blue-500 relative group"
>
Docs
<span className="absolute bottom-[-5px] left-0 w-0 h-0.5 bg-blue-500 transition-all duration-300 group-hover:w-full"></span>
</Link>
<Link
href="/examples"
className="text-white font-medium text-base transition-colors duration-300 hover:text-blue-500 relative group"
>
Examples
<span className="absolute bottom-[-5px] left-0 w-0 h-0.5 bg-blue-500 transition-all duration-300 group-hover:w-full"></span>
</Link>
<Link
href="/about"
className="text-white font-medium text-base transition-colors duration-300 hover:text-blue-500 relative group"
>
About
<span className="absolute bottom-[-5px] left-0 w-0 h-0.5 bg-blue-500 transition-all duration-300 group-hover:w-full"></span>
</Link>
</nav>
</header>
)
}
export default Header`
);
// Footer com Tailwind
fs.writeFileSync(
"src/components/layout/footer/Footer.tsx",
`'use client'
import { FaGithub, FaInstagram, FaLinkedin } from 'react-icons/fa'
const getCurrentYear = () => {
const date = new Date()
return date.getFullYear()
}
const Footer = () => {
return (
<footer className="bg-[#011627] border-t border-[#023864] py-10 px-5 mt-auto">
<div className="max-w-6xl mx-auto flex flex-col items-center gap-5 text-center">
<p className="text-white text-sm opacity-80">
© {getCurrentYear()} RNT Projects. Todos os direitos reservados.
</p>
<div className="flex gap-5 items-center">
<a
href="https://github.com"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
className="text-white transition-all duration-300 p-2 rounded-full flex items-center justify-center hover:text-blue-500 hover:bg-blue-500/10 hover:-translate-y-1"
>
<FaGithub size={24} />
</a>
<a
href="https://linkedin.com"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn"
className="text-white transition-all duration-300 p-2 rounded-full flex items-center justify-center hover:text-blue-500 hover:bg-blue-500/10 hover:-translate-y-1"
>
<FaLinkedin size={24} />
</a>
<a
href="https://instagram.com"
target="_blank"
rel="noopener noreferrer"
aria-label="Instagram"
className="text-white transition-all duration-300 p-2 rounded-full flex items-center justify-center hover:text-blue-500 hover:bg-blue-500/10 hover:-translate-y-1"
>
<FaInstagram size={24} />
</a>
</div>
</div>
</footer>
)
}
export default Footer`
);
}
}
main().catch(console.error);