hardhat-deploy
Version:
Hardhat plugin for replicable smart contract deployments and easy testing across multiple EVM chains, with support for proxies, diamonds, named accounts, and deployment fixtures
194 lines • 7.37 kB
JavaScript
import { Command } from 'commander';
import { readFileSync, readdirSync, mkdirSync, copyFileSync, existsSync, writeFileSync, statSync } from 'fs';
import { join, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
import * as readline from 'readline';
import pkg from '../package.json' with { type: 'json' };
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const program = new Command();
// Get the current version of hardhat-deploy
const getHardhatDeployVersion = () => {
return pkg.version;
};
const askFolder = async () => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question('Enter folder path (default: ./): ', (answer) => {
rl.close();
resolve(answer.trim() || './');
});
});
};
const askAutoInstall = async () => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question('Auto-install dependencies with pnpm? (Y/n): ', (answer) => {
rl.close();
const trimmed = answer.trim().toLowerCase();
resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');
});
});
};
const isFolderEmpty = (folderPath) => {
if (!existsSync(folderPath)) {
return true;
}
try {
const files = readdirSync(folderPath);
return files.length === 0;
}
catch (error) {
// If we can't read the directory, treat it as not empty
return false;
}
};
const copyFile = (source, target, replacements = {}, gitignorePatterns = []) => {
const fileName = basename(source);
// Check if file should be skipped based on gitignore patterns
for (const pattern of gitignorePatterns) {
if (fileName === pattern || fileName.endsWith(pattern.replace('*', ''))) {
return; // Skip this file
}
}
let content = readFileSync(source, 'utf-8');
// Apply replacements
for (const [search, replace] of Object.entries(replacements)) {
content = content.replaceAll(search, replace);
}
mkdirSync(dirname(target), { recursive: true });
// For binary files, just copy as-is
if (source.endsWith('.lock') || source.endsWith('.so') || source.endsWith('.wasm')) {
copyFileSync(source, target);
}
else {
writeFileSync(target, content, 'utf-8');
}
};
const parseGitignore = (gitignorePath) => {
if (!existsSync(gitignorePath)) {
return [];
}
const content = readFileSync(gitignorePath, 'utf-8');
return content
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
};
const copyFolder = (source, target, replacements = {}, gitignorePatterns = []) => {
if (!existsSync(target)) {
mkdirSync(target, { recursive: true });
}
const files = readdirSync(source);
files.forEach((file) => {
const sourcePath = join(source, file);
const targetPath = join(target, file);
const stat = statSync(sourcePath);
if (stat.isDirectory()) {
// Check if directory should be skipped based on gitignore patterns
const shouldSkip = gitignorePatterns.some((pattern) => file === pattern.replace('/', '') || (pattern.startsWith('/') && file === pattern.slice(1)));
if (!shouldSkip) {
copyFolder(sourcePath, targetPath, replacements, gitignorePatterns);
}
}
else {
copyFile(sourcePath, targetPath, replacements, gitignorePatterns);
}
});
};
const generateProject = (targetFolder, projectName) => {
// find template in published package
const templatePath = join(__dirname, '../templates/basic');
const gitignorePath = join(templatePath, '.gitignore');
// Parse gitignore patterns
const gitignorePatterns = parseGitignore(gitignorePath);
// Determine project name from folder or use placeholder
const folderName = projectName || basename(targetFolder === './' ? process.cwd() : targetFolder);
// Get the current version of hardhat-deploy
const hardhatDeployVersion = getHardhatDeployVersion();
const replacements = {
'template-hardhat-node-test-runner': `${folderName}`,
'workspace:*': hardhatDeployVersion,
};
console.log(`Generating project in: ${targetFolder}`);
copyFolder(templatePath, targetFolder, replacements, gitignorePatterns);
console.log('✓ Project initialized successfully!');
};
const runPnpmInstall = async (folderPath) => {
console.log(`Installing dependencies...`);
const { spawn } = await import('child_process');
return new Promise((resolve, reject) => {
// Use --ignore-workspace to ensure dependencies are installed locally
// This prevents pnpm from treating the target folder as part of a parent workspace
const pnpm = spawn('pnpm', ['install', '--ignore-workspace', `--no-frozen-lockfile`], {
cwd: folderPath,
stdio: 'inherit',
});
pnpm.on('close', (code) => {
if (code === 0) {
console.log('✓ Dependencies installed successfully!');
resolve();
}
else {
reject(new Error(`pnpm install failed with exit code ${code}`));
}
});
pnpm.on('error', (error) => {
reject(error);
});
});
};
program.name('hardhat-deploy').description('CLI for hardhat-deploy').version(pkg.version);
program
.command('init')
.argument('[folder]', 'folder to initialize the project in')
.option('--install', 'auto-install dependencies with pnpm')
.description('Initialize a new hardhat-deploy project')
.action(async (folder, options) => {
let targetFolder = folder;
let autoInstall = options?.install ?? false;
// If no folder specified, ask user
if (!targetFolder) {
targetFolder = await askFolder();
// If we prompted for folder, also prompt for auto-install
autoInstall = await askAutoInstall();
}
// Normalize path
targetFolder = targetFolder.trim();
// Check if folder is empty
if (!isFolderEmpty(targetFolder)) {
console.error(`Error: Folder "${targetFolder}" is not empty. Please specify an empty folder or a new folder path.`);
process.exit(1);
}
// Generate project
generateProject(targetFolder);
// Auto-install if requested
if (autoInstall) {
try {
await runPnpmInstall(targetFolder);
}
catch (error) {
console.error('Failed to install dependencies:', error);
console.log('\nYou can install dependencies manually:');
console.log(` cd ${targetFolder === './' ? '.' : targetFolder}`);
console.log(' pnpm install');
process.exit(1);
}
}
// Show next steps
console.log(`\nNext steps:`);
console.log(` cd ${targetFolder === './' ? '.' : targetFolder}`);
if (!autoInstall) {
console.log(` pnpm install`);
}
console.log(` pnpm hardhat test`);
});
program.parse();
//# sourceMappingURL=cli.js.map