UNPKG

stratakit

Version:

stratakit - Meta-framework React puro con Auto Router automΓ‘tico, file-based routing, SEO automΓ‘tico y performance superior

431 lines (400 loc) β€’ 12.2 kB
#!/usr/bin/env node "use strict"; const { Command } = require("commander"); const fs = require("fs-extra"); const path = require("path"); const chalk = require("chalk"); const ora = require("ora"); const inquirer = require("inquirer"); const program = new Command(); program .name("create-stratakit") .description("Create a new stratakit Meta-Framework application") .version("1.6.1") .argument("[project-name]", "Name of the project to create") .option("-t, --template <template>", "Template to use", "default") .option("-y, --yes", "Skip prompts and use default values") .action(async (projectName, options) => { try { const spinner = ora("Creating your React application...").start(); // Get project name let finalProjectName = projectName; if (!finalProjectName) { const answers = await inquirer.prompt([ { type: "input", name: "projectName", message: "What is your project name?", default: "my-stratkit-app", validate: (input) => { if (!input.trim()) { return "Project name is required"; } if (!/^[a-zA-Z0-9-_]+$/.test(input)) { return "Project name can only contain letters, numbers, hyphens, and underscores"; } return true; } } ]); finalProjectName = answers.projectName; } const projectPath = path.resolve(finalProjectName); // Check if directory already exists if (await fs.pathExists(projectPath)) { spinner.fail(`Directory ${finalProjectName} already exists`); process.exit(1); } spinner.text = "Creating project structure..."; await createProjectStructure(projectPath, finalProjectName, options); spinner.text = "Installing dependencies..."; await installDependencies(projectPath); spinner.succeed("Project created successfully!"); console.log(chalk.green("\nπŸŽ‰ Project created successfully!")); console.log(chalk.blue("\nNext steps:")); console.log(chalk.white(` cd ${finalProjectName}`)); console.log(chalk.white(" pnpm dev")); console.log(chalk.white("\nHappy coding! πŸš€")); } catch (error) { console.error(chalk.red("Error creating project:"), error.message); process.exit(1); } }); async function createProjectStructure(projectPath, projectName, options) { await fs.ensureDir(projectPath); // Create app directory structure await fs.ensureDir(path.join(projectPath, "app")); await fs.ensureDir(path.join(projectPath, "app/blog")); await fs.ensureDir(path.join(projectPath, "public")); // Create configuration files await createConfigFiles(projectPath, projectName, options); // Create app files await createAppFiles(projectPath, projectName, options); // Create example pages await createAutoRouterPages(projectPath, projectName, options); } async function createConfigFiles(projectPath, projectName, options) { // package.json const packageJson = { name: projectName, version: "1.0.0", description: "A stratkit Meta-Framework application", type: "module", scripts: { dev: "vite", build: "vite build", preview: "vite preview", "type-check": "tsc --noEmit", }, dependencies: { react: "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^6.28.0", "stratkit": "^1.6.4", }, devDependencies: { "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^5.0.3", typescript: "^5.9.2", vite: "^7.1.7", }, }; await fs.writeJSON(path.join(projectPath, "package.json"), packageJson, { spaces: 2, }); // tsconfig.json const tsconfig = { compilerOptions: { target: "ES2020", useDefineForClassFields: true, lib: ["ES2020", "DOM", "DOM.Iterable"], module: "ESNext", skipLibCheck: true, moduleResolution: "bundler", allowImportingTsExtensions: true, resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: "react-jsx", strict: true, noUnusedLocals: true, noUnusedParameters: true, noFallthroughCasesInSwitch: true, }, include: ["**/*.ts", "**/*.tsx"], exclude: ["node_modules"], }; await fs.writeJSON(path.join(projectPath, "tsconfig.json"), tsconfig, { spaces: 2, }); // vite.config.ts const viteConfig = `import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], optimizeDeps: { exclude: ["stratkit"] }, build: { outDir: 'dist', sourcemap: true, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], router: ['react-router-dom'], }, chunkFileNames: "assets/[name]-[hash].js", entryFileNames: "assets/[name]-[hash].js", assetFileNames: "assets/[name]-[hash].[ext]", }, }, }, server: { port: 5173, open: true, }, });`; await fs.writeFile(path.join(projectPath, "vite.config.ts"), viteConfig); // index.html const indexHtml = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>${projectName}</title> </head> <body> <div id="root"></div> <script type="module" src="/app/main.tsx"></script> </body> </html>`; await fs.writeFile(path.join(projectPath, "index.html"), indexHtml); // .gitignore const gitignore = `# Dependencies node_modules/ .pnpm-store/ # Build outputs dist/ build/ # Environment variables .env .env.local .env.development.local .env.test.local .env.production.local # IDE .vscode/ .idea/ *.swp *.swo # OS .DS_Store Thumbs.db # Logs npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Runtime data pids *.pid *.seed *.pid.lock # Coverage directory used by tools like istanbul coverage/ *.lcov # nyc test coverage .nyc_output # Dependency directories jspm_packages/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Storybook build outputs .out .storybook-out # Temporary folders tmp/ temp/`; await fs.writeFile(path.join(projectPath, ".gitignore"), gitignore); } async function createAppFiles(projectPath, projectName, options) { // App.tsx const appTsx = `import React from "react"; import { AutoRouterProvider, AutoRouteRenderer } from "stratkit"; const LoadingSpinner = () => ( <div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh", fontSize: "18px", color: "#666" }}> Loading... </div> ); const ErrorFallback = ({ error }: { error: Error }) => ( <div style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", height: "100vh", padding: "20px", textAlign: "center" }}> <h2 style={{ color: "#e74c3c", marginBottom: "16px" }}>Something went wrong</h2> <p style={{ color: "#666", marginBottom: "16px" }}>{error.message}</p> <button onClick={() => window.location.reload()} style={{ padding: "8px 16px", backgroundColor: "#3498db", color: "white", border: "none", borderRadius: "4px", cursor: "pointer" }} > Reload Page </button> </div> ); function App() { return ( <AutoRouterProvider fallback={<LoadingSpinner />} errorFallback={ErrorFallback} > <AutoRouteRenderer /> </AutoRouterProvider> ); } export default App;`; await fs.writeFile(path.join(projectPath, "app/App.tsx"), appTsx); // main.tsx const mainTsx = `import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <App /> </React.StrictMode> );`; await fs.writeFile(path.join(projectPath, "app/main.tsx"), mainTsx); } async function createAutoRouterPages(projectPath, projectName, options) { // index.tsx (Home page) const indexTsx = `import React from "react"; export default function HomePage() { return ( <div style={{ padding: "20px", maxWidth: "800px", margin: "0 auto" }}> <h1>Welcome to ${projectName}</h1> <p>This is your home page created with stratkit Meta-Framework!</p> <p>Features:</p> <ul> <li>βœ… Auto Router - File-based routing like Next.js</li> <li>βœ… TypeScript support</li> <li>βœ… Vite for fast development</li> <li>βœ… React 19</li> <li>βœ… Modern tooling</li> </ul> <p> <a href="/about" style={{ color: "#3498db", textDecoration: "none" }}> Go to About page β†’ </a> </p> </div> ); }`; await fs.writeFile(path.join(projectPath, "app/index.tsx"), indexTsx); // about.tsx const aboutTsx = `import React from "react"; export default function AboutPage() { return ( <div style={{ padding: "20px", maxWidth: "800px", margin: "0 auto" }}> <h1>About ${projectName}</h1> <p>This is the about page created with stratkit Meta-Framework.</p> <p>stratkit provides:</p> <ul> <li>Automatic file-based routing</li> <li>SEO optimization</li> <li>Performance monitoring</li> <li>PWA support</li> <li>And much more!</li> </ul> <p> <a href="/" style={{ color: "#3498db", textDecoration: "none" }}> ← Back to Home </a> </p> </div> ); }`; await fs.writeFile(path.join(projectPath, "app/about.tsx"), aboutTsx); // blog/[slug].tsx (Dynamic route) const blogSlugTsx = `import React from "react"; import { useRoute } from "stratkit"; export default function BlogPost() { const { params } = useRoute(); const slug = params.slug; return ( <div style={{ padding: "20px", maxWidth: "800px", margin: "0 auto" }}> <h1>Blog Post: {slug}</h1> <p>This is a dynamic route example. The slug is: <strong>{slug}</strong></p> <p>You can access this page by visiting: <code>/blog/{slug}</code></p> <p> <a href="/" style={{ color: "#3498db", textDecoration: "none" }}> ← Back to Home </a> </p> </div> ); }`; await fs.writeFile(path.join(projectPath, "app/blog/[slug].tsx"), blogSlugTsx); } async function installDependencies(projectPath) { const { exec } = require("child_process"); const util = require("util"); const execAsync = util.promisify(exec); try { await execAsync("pnpm install", { cwd: projectPath }); } catch (error) { console.warn("Failed to install with pnpm, trying npm..."); try { await execAsync("npm install", { cwd: projectPath }); } catch (npmError) { console.warn("Failed to install dependencies automatically"); console.log("Please run 'pnpm install' or 'npm install' manually"); } } } program.parse();