UNPKG

vitesse-dependency-scripts

Version:

Automated toolkit for managing dependencies with pnpm catalogs in Vue/Vite projects

498 lines (433 loc) • 14 kB
#!/usr/bin/env node /** * Create New Vitesse Project + Auto Update (All-in-One) * * This unified script combines: * 1. create-vitesse-project.js - Clone Vitesse and setup * 2. auto-update-full.js - Fetch latest versions and update * * Result: A brand new project with latest dependencies in one command! */ import { execSync } from "child_process"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { dirname } from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Always use current working directory where the user ran the command // This works correctly for: npm install -g, npm link, npx, and direct node execution const targetRoot = process.cwd(); // Custom dependencies to add to the new project const CUSTOM_DEPENDENCIES = { frontend: [ "@casl/ability", "@casl/vue", "@fortawesome/fontawesome-svg-core", "@vuelidate/core", "@vuelidate/validators", "@vueuse/head", "@vueuse/integrations", "axios", "browser-image-compression", "chart.js", "element-plus", "luxon", "prismjs", "universal-cookie", "virtual-scroller", "vue-axios", "vue-chartjs", "vue-demi", "vuedraggable", "vue-virtual-scroller", ], dev: ["@iconify-json/ic", "@iconify-json/mdi"], build: [ "critters", "cross-env", "sass", "ts-node", "unplugin-element-plus", "vite-plugin-pages", "vite-plugin-vue-component-preview", ], types: ["@types/luxon", "@types/node", "@types/prismjs"], }; const CATALOG_TO_DEP_TYPE = { frontend: "dependencies", dev: "devDependencies", build: "devDependencies", types: "devDependencies", }; /** * Print colored messages */ const colors = { reset: "\x1b[0m", bright: "\x1b[1m", green: "\x1b[32m", blue: "\x1b[34m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m", magenta: "\x1b[35m", }; function log(message, color = colors.reset) { console.log(`${color}${message}${colors.reset}`); } /** * Get project name from command line or prompt */ function getProjectName() { const args = process.argv.slice(2); if (args.length > 0) { return args[0]; } return null; } /** * Ensure pnpm is installed */ function ensurePnpm() { try { execSync("pnpm --version", { stdio: "ignore" }); return true; } catch { log(" šŸ“¦ Installing pnpm globally...", colors.yellow); try { execSync("npm install -g pnpm", { stdio: "inherit" }); log(" āœ… pnpm installed successfully!", colors.green); return true; } catch (error) { log( " āŒ Failed to install pnpm. Please install it manually: npm install -g pnpm", colors.red ); return false; } } } /** * Clone Vitesse template */ function cloneVitesse(projectName) { log( `\nšŸš€ Creating new Vitesse project: ${projectName}`, colors.cyan + colors.bright ); try { // Check if degit is available, if not install it temporarily try { execSync("npx degit --version", { stdio: "ignore" }); } catch { log(" šŸ“¦ Installing degit...", colors.yellow); } log(" šŸ“„ Cloning Vitesse template...", colors.blue); const projectPath = path.join(targetRoot, projectName); // Change to target directory before running degit const originalDir = process.cwd(); process.chdir(targetRoot); execSync(`npx degit antfu-collective/vitesse ${projectName}`, { stdio: "inherit", }); // Return to original directory process.chdir(originalDir); log(` āœ… Project created at ${projectPath}`, colors.green); return true; } catch (error) { log(` āŒ Failed to clone Vitesse template: ${error.message}`, colors.red); return false; } } /** * Get latest version from npm */ function getLatestVersion(packageName) { try { const version = execSync(`pnpm view ${packageName} version`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], }).trim(); return `^${version}`; } catch (error) { return "latest"; } } /** * Format package name for YAML */ function formatPackageName(pkg) { if (pkg.includes("@") || pkg.includes("/") || pkg.includes(".")) { return `'${pkg}'`; } return pkg; } /** * Check if package exists in workspace */ function packageExistsInWorkspace(content, packageName) { const patterns = [ new RegExp( `^ '${packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}':`, "m" ), new RegExp( `^ ${packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:`, "m" ), ]; return patterns.some((pattern) => pattern.test(content)); } /** * Add packages to catalog section */ function addToCatalog(content, catalogName, packages) { const catalogPattern = new RegExp(`^ ${catalogName}:$`, "m"); const match = catalogPattern.exec(content); if (!match) { log(` āš ļø Catalog '${catalogName}' not found, skipping...`, colors.yellow); return content; } const catalogIndex = match.index + match[0].length; const afterCatalog = content.substring(catalogIndex); const lines = afterCatalog.split("\n"); let endLineIndex = lines.length; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.match(/^ [a-z]+:$/) || line.match(/^[a-z]+:/i)) { endLineIndex = i; break; } } const catalogLines = lines.slice(0, endLineIndex); const afterCatalogLines = lines.slice(endLineIndex); let newEntries = []; for (const [pkg, version] of packages) { if (packageExistsInWorkspace(content, pkg)) { log(` ā­ļø ${pkg} already exists`, colors.blue); continue; } const formattedPkg = formatPackageName(pkg); newEntries.push(` ${formattedPkg}: ${version}`); log(` āœ… ${pkg}: ${version}`, colors.green); } if (newEntries.length === 0) { return content; } const beforeCatalog = content.substring(0, catalogIndex); const reconstructedCatalog = "\n" + catalogLines.join("\n") + "\n" + newEntries.join("\n"); const reconstructedAfter = afterCatalogLines.length > 0 ? "\n" + afterCatalogLines.join("\n") : ""; return beforeCatalog + reconstructedCatalog + reconstructedAfter; } /** * Update workspace file with custom dependencies (with version fetching) */ function updateWorkspace(projectPath) { log( "\nšŸ“ Updating pnpm-workspace.yaml with latest versions...", colors.cyan + colors.bright ); const workspaceFile = path.join(projectPath, "pnpm-workspace.yaml"); if (!fs.existsSync(workspaceFile)) { log(" āŒ pnpm-workspace.yaml not found!", colors.red); return false; } let content = fs.readFileSync(workspaceFile, "utf8"); // Process each catalog for (const [catalog, packages] of Object.entries(CUSTOM_DEPENDENCIES)) { log( `\n šŸ“¦ Processing '${catalog}' catalog (${packages.length} packages)...`, colors.magenta ); // Fetch latest versions from npm const packagesWithVersions = packages.map((pkg) => { process.stdout.write(` šŸ” Fetching ${pkg}... `); const version = getLatestVersion(pkg); process.stdout.write(`${version}\n`); return [pkg, version]; }); log(`\n šŸ’¾ Adding to workspace file...`, colors.blue); content = addToCatalog(content, catalog, packagesWithVersions); } fs.writeFileSync(workspaceFile, content, "utf8"); log("\n āœ… Workspace file updated successfully!", colors.green); return true; } /** * Update package.json with catalog references */ function updatePackageJson(projectPath) { log( "\nšŸ“ Updating package.json with catalog references...", colors.cyan + colors.bright ); const packageFile = path.join(projectPath, "package.json"); if (!fs.existsSync(packageFile)) { log(" āŒ package.json not found!", colors.red); return false; } const pkg = JSON.parse(fs.readFileSync(packageFile, "utf8")); let totalAdded = 0; for (const [catalogName, packages] of Object.entries(CUSTOM_DEPENDENCIES)) { const depType = CATALOG_TO_DEP_TYPE[catalogName]; if (!pkg[depType]) { pkg[depType] = {}; } for (const packageName of packages) { if (!pkg[depType][packageName]) { pkg[depType][packageName] = `catalog:${catalogName}`; log(` āœ… ${packageName} → catalog:${catalogName}`, colors.green); totalAdded++; } else { log(` ā­ļø ${packageName} already in package.json`, colors.blue); } } } fs.writeFileSync(packageFile, JSON.stringify(pkg, null, 2) + "\n", "utf8"); log(`\n āœ… Added ${totalAdded} catalog references`, colors.green); return true; } /** * Install dependencies */ function installDependencies(projectPath) { log("\nšŸ“¦ Installing dependencies with pnpm...", colors.cyan + colors.bright); log(" ā³ This may take a few minutes...\n", colors.yellow); try { process.chdir(projectPath); execSync("pnpm install", { stdio: "inherit" }); log("\n āœ… Dependencies installed successfully!", colors.green); return true; } catch (error) { log("\n āŒ Failed to install dependencies", colors.red); return false; } } /** * Print next steps */ function printNextSteps(projectName) { log("\n" + "═".repeat(65), colors.cyan); log("šŸŽ‰ PROJECT READY! šŸŽ‰", colors.green + colors.bright); log("═".repeat(65), colors.cyan); log( "\n✨ Your new Vitesse project with latest dependencies is ready!", colors.cyan ); log("\nšŸ“‹ To start developing:", colors.cyan + colors.bright); log(`\n cd ${projectName}`, colors.bright); log(` pnpm dev\n`, colors.bright); log("🌐 Your app will be available at:", colors.blue); log(" → http://localhost:3333\n", colors.bright); log("šŸ“š Available commands:", colors.cyan); log(" pnpm dev - Start development server", colors.reset); log(" pnpm build - Build for production", colors.reset); log(" pnpm preview - Preview production build", colors.reset); log(" pnpm test - Run unit tests", colors.reset); log(" pnpm test:e2e - Run E2E tests\n", colors.reset); log("šŸ“ Customize your project:", colors.yellow); log(" • Update title in src/App.vue", colors.reset); log(" • Change favicon in public/", colors.reset); log(" • Update LICENSE with your name", colors.reset); log(" • Edit README.md\n", colors.reset); log("═".repeat(65), colors.cyan); log("Happy coding! šŸš€", colors.green + colors.bright); log("═".repeat(65) + "\n", colors.cyan); } /** * Main function */ async function main() { log( "╔═════════════════════════════════════════════════════════════════╗", colors.cyan ); log( "ā•‘ šŸ•ļø Vitesse Instant Project Creator (All-in-One) ā•‘", colors.cyan + colors.bright ); log( "ā•‘ šŸ“¦ Create + Update + Install = One Command ā•‘", colors.cyan ); log( "ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•", colors.cyan ); const projectName = getProjectName(); if (!projectName) { log("\nāŒ Please provide a project name:", colors.red); log(`\n node create-project-instant.js my-awesome-app\n`, colors.bright); log("šŸ’” This will:", colors.cyan); log(" 1. Clone Vitesse template", colors.reset); log(" 2. Fetch latest package versions from npm", colors.reset); log(" 3. Configure pnpm workspace catalogs", colors.reset); log(" 4. Update package.json", colors.reset); log(" 5. Install all dependencies\n", colors.reset); process.exit(1); } // Check if directory already exists const projectPath = path.join(targetRoot, projectName); if (fs.existsSync(projectPath)) { log(`\nāŒ Directory '${projectName}' already exists!`, colors.red); log( `\nšŸ’” Try a different name or remove the existing directory:\n`, colors.yellow ); log(` rm -rf ${projectName}`, colors.reset); log(` node create-project-instant.js ${projectName}\n`, colors.reset); process.exit(1); } // Ensure pnpm is installed if (!ensurePnpm()) { process.exit(1); } const originalDir = process.cwd(); const startTime = Date.now(); try { // Step 1: Clone Vitesse if (!cloneVitesse(projectName)) { process.exit(1); } // Step 2: Update workspace with LATEST versions from npm if (!updateWorkspace(projectPath)) { log( "\nāš ļø Failed to update workspace file, but project was created", colors.yellow ); } // Step 3: Update package.json with catalog references if (!updatePackageJson(projectPath)) { log( "\nāš ļø Failed to update package.json, but project was created", colors.yellow ); } // Step 4: Install dependencies if (!installDependencies(projectPath)) { log("\nāš ļø Installation incomplete. You may need to run:", colors.yellow); log(`\n cd ${projectName} && pnpm install\n`, colors.bright); } // Return to original directory process.chdir(originalDir); // Calculate elapsed time const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); log(`\nā±ļø Total time: ${elapsed}s`, colors.cyan); // Print next steps printNextSteps(projectName); } catch (error) { log(`\nāŒ Error: ${error.message}`, colors.red); log(`\nšŸ“š Stack trace:`, colors.yellow); console.error(error); process.chdir(originalDir); process.exit(1); } } // Run the script main().catch((error) => { console.error("āŒ Unexpected error:", error); process.exit(1); });