UNPKG

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.

174 lines (173 loc) 7.87 kB
"use strict"; 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 chalk_1 = __importDefault(require("chalk")); 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, level = 0, selectedContracts) => { printArr.forEach(el => { console.log(`${" ".repeat(level)}${selectedContracts.includes(el.filePath) ? chalk_1.default.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), ]; }, []); };