@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
149 lines (148 loc) • 8.56 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.renameContractsAndInterfaces = renameContractsAndInterfaces;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
/**
* Renames all contract and interface files in a given directory (recursively) by appending the provided version (e.g., V1).
* Also updates the file contents to reflect the new contract names and handles import statements accordingly.
* Skips files that are in the whitelist.
*
* @param {string} directoryPath - Relative path to the directory containing the contracts/interfaces.
* @param {string} version - The version to append to the contract/interface names (e.g., "1" for V1).
* @param {string[]} contractsWhitelist - Array of contract names to skip from renaming (without file extensions, e.g., ["B3TR"]).
* @param {string[]} interfacesWhitelist - Array of interface names to skip from renaming (without file extensions, e.g., ["IERC6372"]).
* @param {string[]} usagesToUpdate - Array of contract/interface names that should be updated in the codebase (e.g., ["IVoterRewards"]) even if they are not renamed through imports.
*
* @throws Will throw an error if the directory does not exist or if a file read/write fails.
*/
async function renameContractsAndInterfaces(directoryPath, version, contractsWhitelist, interfacesWhitelist, usagesToUpdate) {
try {
const contractsPath = path_1.default.join(__dirname, "..", "contracts", directoryPath);
const renameFilesRecursively = async (currentPath) => {
const files = await promises_1.default.readdir(currentPath);
await Promise.all(files.map(async (file) => {
const fullPath = path_1.default.join(currentPath, file);
const stat = await promises_1.default.lstat(fullPath);
// If it's a directory, recurse into it
if (stat.isDirectory()) {
await renameFilesRecursively(fullPath);
}
else if (file.endsWith(".sol")) {
const fileNameWithoutExt = file.replace(".sol", "");
// Skip files in the whitelist
if (contractsWhitelist.includes(fileNameWithoutExt)) {
console.log(`Skipping whitelist file: ${file}`);
return;
}
let content = await promises_1.default.readFile(fullPath, "utf8");
// Define the new file name with version
const newName = file.replace(".sol", `V${version}.sol`);
// Gather import names to update occurrences starting with the usages to update
const importRenames = usagesToUpdate.map(name => ({
originalName: name,
newName: `${name}V${version}`,
}));
// Update imports (ignore imports starting with '@')
content = content.replace(/import\s+{([^}]+)}\s+from\s+["'](.+)\.sol["']/g, (match, imports, importPath) => {
if (!importPath.startsWith("@")) {
const updatedImports = imports
.split(",")
.map((imp) => {
const originalName = imp.trim();
// Skip whitelisted imports
if (interfacesWhitelist.includes(originalName)) {
return originalName;
}
const newName = `${originalName}V${version}`;
importRenames.push({ originalName, newName }); // Track names to replace later
return newName;
})
.join(", ");
return `import { ${updatedImports} } from "${importPath}V${version}.sol"`;
}
return match;
});
// Update simple import lines (without named imports)
content = content.replace(/import\s+["'](.+)\.sol["']/g, (match, p1) => {
if (!p1.startsWith("@")) {
const importPath = p1.split("/").pop() || ""; // Get the last part of the path (file name)
const originalName = importPath.replace(".sol", ""); // Extract the contract name without extension
// Skip whitelisted imports
if (interfacesWhitelist.includes(originalName)) {
return match;
}
const newName = `${originalName}V${version}`; // Append version to contract name
importRenames.push({ originalName, newName }); // Track it for later usage replacement
return `import "${p1}V${version}.sol"`;
}
return match;
});
// Only update `contract`, `interface` or `library` declarations in the code (ignore comments)
content = content.replace(/^[ \t]*(contract|interface|library|abstract contract)\s+(\w+)/gm, // Optimized to avoid backtracking
(match, type, name) => {
if (interfacesWhitelist.includes(name)) {
return match;
}
// Add to import renames
importRenames.push({ originalName: name, newName: `${name}V${version}` });
return `${type} ${name}V${version}`;
});
// Replace instances of the imported interfaces and contracts in the content
importRenames.forEach(({ originalName, newName }) => {
// Create a regex that only matches if the original name is not preceded by a dot
const usagePattern = new RegExp(`(?<!\\.)\\b${originalName}\\b`, "g");
content = content.replace(usagePattern, newName);
});
// Write updated content to new file
await promises_1.default.writeFile(path_1.default.join(currentPath, newName), content);
// Delete old file
await promises_1.default.unlink(fullPath);
console.log(`Renamed and updated: ${file} -> ${newName}`);
}
}));
};
// Start recursive renaming
await renameFilesRecursively(contractsPath);
}
catch (error) {
console.error(`Error processing directory '${directoryPath}': ${error}`);
process.exit(1);
}
}
// Script execution
/**
* Parses arguments passed via command line and executes the renaming function.
* Expects exactly two arguments: the path to the contracts directory and the version string.
*/
async function run() {
const args = process.argv.slice(2);
if (args.length !== 2) {
console.error("Usage: <path> <version>");
process.exit(1);
}
const [directoryPath, version] = args;
// Validate the version format (e.g., allow numbers only)
if (!/^\d+$/.test(version)) {
console.error("Invalid version format. Only numeric values are allowed (e.g., 1, 2, 3).");
process.exit(1);
}
// Whitelist of contract names that should not be renamed (without ".sol" extension)
const contractsWhitelist = ["B3TR", "B3TRProxy"];
// Whitelist of interface names that should not be renamed
const interfacesWhitelist = ["IERC6372", "Checkpoints"];
// List of contract/interface names that should be updated in the codebase even if they are not renamed through imports
const usagesToUpdate = ["IVoterRewards", "IEmissions", "IX2EarnApps"];
await renameContractsAndInterfaces(directoryPath, version, contractsWhitelist, interfacesWhitelist, usagesToUpdate);
}
run()
.then(() => {
console.log("Contract renaming completed successfully.");
})
.catch(error => {
console.error("Error running contract renaming script:", error);
process.exit(1);
});