locklift
Version:
Node JS framework for working with Ever contracts. Inspired by Truffle and Hardhat. Helps you to build, test, run and maintain your smart contracts.
175 lines (174 loc) • 7.87 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuildCache = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const utils_1 = require("../cli/builder/utils");
const utils_2 = require("./utils");
const rxjs_1 = require("rxjs");
const lodash_1 = __importDefault(require("lodash"));
const cacheFolder = path_1.default.join(".cache/build.json");
const importMatcher = /^\s*import\s*(?:{[^}]+}\s*from\s*)?["']([^"']+\.t?sol)["']\s*;/gm;
const artifactsExtensions = [".tvc", ".abi.json", ".code"];
class BuildCache {
contracts;
buildFolder;
compilerConfig;
prevCache;
currentCache = {};
constructor(contracts, isForce, buildFolder, compilerConfig) {
this.contracts = contracts;
this.buildFolder = buildFolder;
this.compilerConfig = compilerConfig;
fs_extra_1.default.ensureFileSync(cacheFolder);
this.prevCache = isForce ? [] : fs_extra_1.default.readJSONSync(cacheFolder, { throws: false }) || [];
if (JSON.stringify(this.prevCache.compilerSettings) !== JSON.stringify(this.compilerConfig)) {
this.clearCache();
}
}
getBuiltContracts() {
const files = fs_extra_1.default.readdirSync(this.buildFolder);
return (0, lodash_1.default)(files)
.groupBy(el => el.split(".")[0])
.entries()
.filter(([, files]) => artifactsExtensions.every(ext => files.some(file => file.endsWith(ext))))
.map(([contractName]) => contractName)
.value();
}
async buildTree() {
const builtContracts = this.getBuiltContracts();
const { contractsMap, contractsWithImports } = await this.findContractsAndImports(this.contracts);
Array.from(contractsMap.keys())
.filter(el => !builtContracts.includes(path_1.default.basename(el).split(".")[0]))
.map(pathToContract => pathToContract)
.forEach(el => this.removeRecordFromCache(el));
const uniqFiles = this.getUniqueFiles(contractsWithImports);
const filesWithModTime = this.applyModTime(uniqFiles);
this.currentCache = filesWithModTime;
const updatedOrNewFiles = this.getUpdatedOrNewFiles(filesWithModTime, this.prevCache);
const importToImportersMap = contractsWithImports.reduce((acc, current) => {
current.imports.forEach(imp => {
acc[imp.path] = acc[imp.path] ? [...acc[imp.path], current.path] : [current.path];
});
return acc;
}, {});
const printArr = [];
return findFilesForBuildRecursive(updatedOrNewFiles, importToImportersMap, contractsMap, printArr);
}
async findContractsAndImports(contracts) {
const pathToNodeModules = (0, utils_1.tryToGetNodeModules)();
const contractsMap = new Map();
const contractsWithImports = await (0, rxjs_1.lastValueFrom)((0, rxjs_1.from)(contracts).pipe((0, rxjs_1.mergeMap)(contractPath => (0, rxjs_1.from)(fs_extra_1.default.readFile(contractPath, {
encoding: "utf-8",
})).pipe((0, rxjs_1.tap)(contractFile => {
if (new RegExp(/^\s*contract [A-Za-z0-9_]+\s*(is\s+[A-Za-z0-9_,\s]+)*\{/gm).test(contractFile)) {
contractsMap.set(contractPath, true);
}
}), (0, rxjs_1.mergeMap)(contractFile => {
return (0, rxjs_1.from)(Array.from(contractFile.matchAll(importMatcher))).pipe((0, rxjs_1.map)(el => el[1]), (0, rxjs_1.mergeMap)(imp => (0, rxjs_1.defer)(async () => {
const localImportPath = path_1.default.join(contractPath, "..", imp);
const localFileChangeTime = await (0, utils_2.tryToGetFileChangeTime)(localImportPath);
if (localFileChangeTime) {
return {
path: localImportPath,
modificationTime: localFileChangeTime,
};
}
const nodeModulesImportPath = path_1.default.join(pathToNodeModules, imp);
const nodeModulesFileChangeTime = await (0, utils_2.tryToGetFileChangeTime)(nodeModulesImportPath);
if (nodeModulesFileChangeTime) {
return {
path: nodeModulesImportPath,
modificationTime: nodeModulesFileChangeTime,
};
}
throw new Error(`Can't find import ${imp} for file ${contractPath}`);
})), (0, rxjs_1.toArray)());
}), (0, rxjs_1.map)(imports => ({ path: contractPath, imports })))), (0, rxjs_1.toArray)()));
return { contractsWithImports, contractsMap };
}
getUniqueFiles(contractsWithImports) {
const uniqFiles = new Set();
[
...contractsWithImports.map(el => el.path),
...contractsWithImports.flatMap(el => el.imports.map(el => el.path)),
].forEach(el => uniqFiles.add(el));
return Array.from(uniqFiles);
}
getUpdatedOrNewFiles(filesWithModTime, cache) {
return Object.entries(filesWithModTime)
.filter(([filePath, { modificationTime }]) => {
const prevFile = cache[filePath];
if (!prevFile) {
return true;
}
return prevFile.modificationTime !== modificationTime;
})
.map(([filePath]) => filePath);
}
applyModTime(files) {
return files.reduce((acc, el) => {
return { ...acc, [el]: { modificationTime: fs_extra_1.default.statSync(el).mtime.getTime() } };
}, {});
}
applyCurrentCache() {
fs_extra_1.default.writeJSONSync(cacheFolder, { ...this.currentCache, compilerSettings: this.compilerConfig }, {
spaces: 4,
});
}
applyOldCache() {
fs_extra_1.default.writeJSONSync(cacheFolder, this.prevCache, {
spaces: 4,
});
}
clearCache() {
fs_extra_1.default.writeJSONSync(cacheFolder, [], {
spaces: 4,
});
this.prevCache = {};
}
removeRecordFromCache(filePath) {
delete this.prevCache[filePath];
this.applyOldCache();
}
static clearCache() {
fs_extra_1.default.rmSync(cacheFolder, { recursive: true });
}
}
exports.BuildCache = BuildCache;
// const recursivePrint = (printArr: Array<Print>, level = 0, selectedContracts: Array<string>) => {
// printArr.forEach(el => {
// console.log(
// `${" ".repeat(level)}${selectedContracts.includes(el.filePath) ? chalk.blueBright(el.filePath) : el.filePath}`,
// );
// recursivePrint(el.subDep, level + 1, selectedContracts);
// });
// };
const findFilesForBuildRecursive = (updatedOrNewFiles, importToFileMap, contractsMap, printArr, visitedMap = new Map()) => {
return updatedOrNewFiles.reduce((acc, filePath) => {
const importRecords = importToFileMap[filePath];
const prevVisited = new Map(visitedMap);
if (visitedMap.get(filePath)) {
return acc;
}
visitedMap.set(filePath, true);
/// debug
const newPrintArr = [];
printArr.push({ filePath, subDep: newPrintArr });
if (!importRecords || importRecords.length === 0) {
acc.push(filePath);
return acc;
}
const notVisitedFiles = importRecords.filter(el => !prevVisited.get(el));
if (contractsMap.get(filePath)) {
acc.push(filePath);
}
return [
...acc,
...findFilesForBuildRecursive(notVisitedFiles, importToFileMap, contractsMap, newPrintArr, visitedMap),
];
}, []);
};