UNPKG

vitesse-dependency-scripts

Version:

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

455 lines (391 loc) • 11.8 kB
#!/usr/bin/env node /** * Create New Vitesse Project with Custom Dependencies * * This script: * 1. Clones Vitesse template using degit * 2. Updates pnpm-workspace.yaml with custom dependencies * 3. Updates package.json with catalog references * 4. Runs pnpm install * 5. Optionally cleans up demo files */ 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", }; 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 ./${projectName}`, 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)) { continue; } const formattedPkg = formatPackageName(pkg); newEntries.push(` ${formattedPkg}: ${version}`); } 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 */ function updateWorkspace(projectPath) { log( "\nšŸ“ Updating pnpm-workspace.yaml with custom dependencies...", colors.cyan ); 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( ` šŸ“¦ Adding ${packages.length} packages to '${catalog}' catalog...`, colors.blue ); const packagesWithVersions = packages.map((pkg) => { const version = getLatestVersion(pkg); return [pkg, version]; }); content = addToCatalog(content, catalog, packagesWithVersions); } fs.writeFileSync(workspaceFile, content, "utf8"); log(" āœ… Workspace file updated!", colors.green); return true; } /** * Update package.json with catalog references */ function updatePackageJson(projectPath) { log("\nšŸ“ Updating package.json with catalog references...", colors.cyan); 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}`; totalAdded++; } } } fs.writeFileSync(packageFile, JSON.stringify(pkg, null, 2) + "\n", "utf8"); log( ` āœ… Added ${totalAdded} catalog references to package.json`, colors.green ); return true; } /** * Install dependencies */ function installDependencies(projectPath) { log("\nšŸ“¦ Installing dependencies with pnpm...", colors.cyan); try { process.chdir(projectPath); execSync("pnpm install", { stdio: "inherit" }); log(" āœ… Dependencies installed successfully!", colors.green); return true; } catch (error) { log(" āŒ Failed to install dependencies", colors.red); return false; } } /** * Print next steps */ function printNextSteps(projectName) { log("\n" + "=".repeat(60), colors.cyan); log("šŸŽ‰ Project setup complete!", colors.green + colors.bright); log("=".repeat(60), colors.cyan); log("\nšŸ“‹ Next steps:", colors.cyan); log(`\n cd ${projectName}`, colors.bright); log(` pnpm dev`, colors.bright); log("\n🌐 Your app will be available at http://localhost:3333", colors.blue); log("\nšŸ“š Additional commands:", colors.cyan); log(" pnpm build - Build for production", colors.reset); log(" pnpm test - Run unit tests", colors.reset); log(" pnpm test:e2e - Run E2E tests", colors.reset); log("\nšŸ“ Don't forget to:", colors.yellow); log(" - Update the title in App.vue", colors.reset); log(" - Change the favicon in public/", colors.reset); log(" - Update LICENSE with your name", colors.reset); log(" - Clean up README.md", colors.reset); log("\n"); } /** * Main function */ async function main() { log( "╔════════════════════════════════════════════════════════════╗", colors.cyan ); log( "ā•‘ šŸ•ļø Vitesse Project Creator with Custom Dependencies ā•‘", colors.cyan + colors.bright ); log( "ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•", colors.cyan ); const projectName = getProjectName(); if (!projectName) { log("\nāŒ Please provide a project name:", colors.red); log(`\n node create-vitesse-project.js my-awesome-app\n`, colors.bright); 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); process.exit(1); } // Ensure pnpm is installed if (!ensurePnpm()) { process.exit(1); } const originalDir = process.cwd(); try { // Step 1: Clone Vitesse if (!cloneVitesse(projectName)) { process.exit(1); } // Step 2: Update workspace if (!updateWorkspace(projectPath)) { log( "\nāš ļø Failed to update workspace file, but project was created", colors.yellow ); } // Step 3: Update package.json 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āš ļø You may need to run pnpm install manually", colors.yellow); } // Return to original directory process.chdir(originalDir); // Print next steps printNextSteps(projectName); } catch (error) { log(`\nāŒ Error: ${error.message}`, colors.red); process.chdir(originalDir); process.exit(1); } } // Run the script main().catch((error) => { console.error("āŒ Unexpected error:", error); process.exit(1); });