This script gives me a consistent start for a new project, with my standard logging, utilities, and other things I rely on and would otherwise copy and paste from previous projects.
The motivation for this was that I develop with the Konva canvas lib - if you don't know it check Konva out here - and found myself starting a lot of similar projects lately. I'd evolved a mental template for these projects, but was setting up each by hand. I decided to take time out to see if I could automate that. This is the result.
This doesn't need a lot of explaining so here's the script.
/**
* Setup VW project using Vite template
* Usage: node vwSetup.cjs --targetPath=<new-project-dir> --projectName=<name> [--copyFrom=<template-dir>]
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Get configuration from arguments or npm config
function getArg(name) {
// Try npm config first
const npmValue = process.env[`npm_config_${name}`];
if (npmValue) return npmValue;
// Fall back to command line args
const arg = process.argv.find(arg => arg.startsWith(`--${name}=`));
return arg ? arg.split('=')[1] : null;
}
// Get configuration
const targetPath = getArg('targetPath');
const templatePath = getArg('copyFrom');
const projectName = getArg('projectName') || (targetPath ? path.basename(targetPath) : null);
if (!targetPath) {
console.error('Error: --targetPath is required');
process.exit(1);
}
// Token mappings
const tokens = {
'##productname##': projectName,
};
// Copy template files
function copyTemplate(source, target) {
const files = fs.readdirSync(source);
files.forEach(file => {
const sourcePath = path.join(source, file);
const targetPath = path.join(target, file);
console.log(`Processing: ${sourcePath} -> ${targetPath}`);
const stats = fs.statSync(sourcePath);
if (stats.isDirectory()) {
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath, { recursive: true });
}
copyTemplate(sourcePath, targetPath);
} else {
// Read and process file content
let content = fs.readFileSync(sourcePath, 'utf8');
// Replace tokens
Object.entries(tokens).forEach(([token, value]) => {
content = content.replace(new RegExp(token, 'g'), value);
});
// Write processed content
fs.writeFileSync(targetPath, content);
}
});
}
// Main setup process
try {
console.log(`Setting up project "${projectName}"...`);
// Change to parent directory first
const parentDir = path.dirname(targetPath);
const projectDir = path.basename(targetPath);
console.log(`Creating in: ${parentDir}`);
console.log(`Project folder: ${projectDir}`);
// Create parent directory if it doesn't exist
if (!fs.existsSync(parentDir)) {
fs.mkdirSync(parentDir, { recursive: true });
}
// Change to parent directory
process.chdir(parentDir);
// Create Vite project using just the project folder name
console.log('Creating Vite project...');
execSync(`npm create vite@latest "${projectDir}" -y -- --template vanilla-ts --yes`, { stdio: 'inherit' });
// Change to project directory and install dependencies
process.chdir(projectDir);
console.log(`Installing dependencies in ${projectDir}...`);
execSync('npm install', { stdio: 'inherit' });
// Apply template customizations if template path is provided
if (templatePath) {
console.log('Applying template customizations...');
console.log(`Template source: ${templatePath}`);
copyTemplate(templatePath, '.');
}
console.log('\nSetup completed successfully! 🎉');
console.log('\nNext steps:');
console.log(`1. Change to the project directory:`);
console.log(` cd "${process.cwd()}"`);
console.log(`2. Go to File -> Open Folder`);
console.log(`3. Navigate to: ${process.cwd()}`);
console.log(`4. Start the development server:`);
console.log(' npm run dev\n');
console.log('[When you open the folder in the editor this text will be lost!]\n');
console.log('Happy coding! 🚀\n');
} catch (error) {
console.error('Setup failed:', error.message);
process.exit(1);
}
I really wanted to be able to run the script and just have the editor switch to the new project folder, but because of the sensible security constraints a script running in the command window cannot directly affect the editor it is running within, so we have to get the developer to run the last couple of steps. But that's what we would have had to do for the standard Vite setup anyway, so no net loss.
Summary
We set up our template folder, run the script, switch folders, and we hit the ground running with a beautifully formed project with no need to waste time setting up our preferred project config. Sweet.
Thanks for reading.
VW. April 2025