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
JavaScript
#!/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();