vitesse-dependency-scripts
Version:
Automated toolkit for managing dependencies with pnpm catalogs in Vue/Vite projects
498 lines (433 loc) ⢠14 kB
JavaScript
/**
* 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);
});