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