UNPKG

@factorial-finance/blueprint-node

Version:

blueprint-node-plugin

349 lines (348 loc) 14.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateAliases = generateAliases; exports.findActionAlias = findActionAlias; exports.findErrorAlias = findErrorAlias; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const blueprint_1 = require("@ton/blueprint"); const ts = __importStar(require("typescript")); const WRAPPERS_DIR = path.join(process.cwd(), 'wrappers'); // Recursively get all TypeScript files in a directory function getAllTsFiles(dir, basePath = '') { const compileFiles = []; const regularTsFiles = []; // Check if directory exists if (!fs.existsSync(dir)) { return { compileFiles, regularTsFiles }; } const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); const relativePath = basePath ? path.join(basePath, entry.name) : entry.name; if (entry.isDirectory()) { // Recursively process subdirectories const subFiles = getAllTsFiles(fullPath, relativePath); compileFiles.push(...subFiles.compileFiles); regularTsFiles.push(...subFiles.regularTsFiles); } else if (entry.isFile()) { if (entry.name.endsWith('.compile.ts')) { compileFiles.push(relativePath); } else if (entry.name.endsWith('.ts')) { regularTsFiles.push(relativePath); } } } return { compileFiles, regularTsFiles }; } // Function to extract object literal values function extractObjectLiteral(node) { const result = {}; node.properties.forEach(prop => { if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.initializer) { const name = prop.name.text; let value; if (ts.isNumericLiteral(prop.initializer)) { value = parseInt(prop.initializer.text); } else if (ts.isPrefixUnaryExpression(prop.initializer) && prop.initializer.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(prop.initializer.operand)) { value = -parseInt(prop.initializer.operand.text); } else if (prop.initializer.kind === ts.SyntaxKind.FirstLiteralToken) { // Handle hex literals const text = prop.initializer.getText(); if (text.startsWith('0x')) { value = parseInt(text, 16); } } if (value !== undefined) { result[name] = value; } } }); return result; } // Extract static properties from a TypeScript class function extractStaticProperties(sourceFile, contractName) { const ops = {}; const errors = {}; // Patterns to match for opcodes and errors (case insensitive) const opPatterns = ['op', 'ops', 'opcode', 'opcodes']; const errorPatterns = ['error', 'errors', 'errorcode', 'errorcodes']; // Visit each node in the AST function visit(node) { if (ts.isClassDeclaration(node) && node.name) { // Look for static properties in the class node.members.forEach(member => { if (ts.isPropertyDeclaration(member) && member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword) && ts.isIdentifier(member.name) && member.initializer && ts.isObjectLiteralExpression(member.initializer)) { const propName = member.name.text.toLowerCase(); // case-insensitive const values = extractObjectLiteral(member.initializer); if (Object.keys(values).length > 0) { // Check if it matches any op pattern if (opPatterns.includes(propName)) { Object.assign(ops, values); } // Check if it matches any error pattern else if (errorPatterns.includes(propName)) { Object.assign(errors, values); } } } }); } ts.forEachChild(node, visit); } visit(sourceFile); return { ops, errors }; } // Background compilation function with yielding async function compileContracts(compileFiles) { const CODES = {}; const contractNames = compileFiles.map(file => path.basename(file, '.compile.ts')); console.log(`Compiling ${compileFiles.length} contracts...`); // Process in chunks to yield to event loop const chunkSize = 2; for (let i = 0; i < contractNames.length; i += chunkSize) { const chunk = contractNames.slice(i, i + chunkSize); // Yield to event loop between chunks await new Promise(resolve => setImmediate(resolve)); // Compile chunk in parallel const chunkPromises = chunk.map(async (contractName) => { try { const code = await (0, blueprint_1.compile)(contractName); return { contractName, hash: code.hash().toString('hex') }; } catch (error) { // Silently skip failed compilations return null; } }); const results = await Promise.all(chunkPromises); for (const result of results) { if (result) { CODES[result.contractName] = result.hash; } } } const successCount = Object.keys(CODES).length; if (successCount > 0) { console.log(`Successfully compiled ${successCount} out of ${compileFiles.length} contracts`); } return CODES; } async function generateAliases(aliasServiceInstance) { // Get all TypeScript files recursively const { compileFiles, regularTsFiles } = getAllTsFiles(WRAPPERS_DIR); console.log('Loading contract aliases...\n'); let CODES = {}; // Cache file path const cacheFile = path.join(process.cwd(), 'node_modules', '.cache', 'blueprint-node', 'aliases.json'); // Try to load cached aliases first try { if (fs.existsSync(cacheFile)) { const cachedAliases = JSON.parse(fs.readFileSync(cacheFile, 'utf-8')); console.log('Using cached aliases...'); // Update aliases in background (async () => { try { console.log('Starting background update...'); const freshCodes = await compileContracts(compileFiles); const freshAliases = await generateFreshAliases(freshCodes, compileFiles, regularTsFiles); // Save cache fs.writeFileSync(cacheFile, JSON.stringify(freshAliases, null, 2)); console.log('Alias cache updated'); // Update AliasService instance if provided if (aliasServiceInstance) { aliasServiceInstance.updateAliases(freshAliases); } } catch (error) { console.error('Background update failed:', error); } })().catch(() => { }); return cachedAliases; } } catch (e) { // Ignore cache load failure } // No cache found, return minimal aliases immediately console.log('No cache found, starting with minimal aliases...'); // Start everything in background (async () => { try { console.log('Generating aliases in background...'); // First pass: generate aliases without compilation const initialAliases = await generateFreshAliases({}, compileFiles, regularTsFiles); // Save initial cache const cacheDir = path.dirname(cacheFile); if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); } fs.writeFileSync(cacheFile, JSON.stringify(initialAliases, null, 2)); console.log('Initial aliases cached'); // Second pass: compile and update if (compileFiles.length > 0) { console.log('Starting background compilation...'); const freshCodes = await compileContracts(compileFiles); const freshAliases = await generateFreshAliases(freshCodes, compileFiles, regularTsFiles); fs.writeFileSync(cacheFile, JSON.stringify(freshAliases, null, 2)); console.log('Alias cache updated with compiled contracts'); // Update AliasService instance if provided if (aliasServiceInstance) { aliasServiceInstance.updateAliases(freshAliases); } } } catch (error) { console.error('Background alias generation failed:', error); } })().catch(() => { }); // Return minimal aliases immediately return { CODES: {}, ADDRESSES: {}, OP_ALIASES: {}, ERROR_ALIASES: {} }; } // Actual alias generation logic async function generateFreshAliases(CODES, _compileFiles, regularTsFiles) { // Try to import blueprint.config.ts if it exists let blueprintAliases = null; try { const configPath = path.join(process.cwd(), 'blueprint.config.ts'); if (fs.existsSync(configPath)) { // Load blueprint.config.ts using dynamic import const configModule = await Promise.resolve(`${configPath}`).then(s => __importStar(require(s))); blueprintAliases = configModule.aliases || configModule.default?.aliases; console.log('Loaded aliases from blueprint.config.ts'); } } catch (error) { console.log('blueprint.config.ts not found or failed to load, continuing...'); } // Extract Op and Error from regular ts files const OP_ALIASES = {}; const ERROR_ALIASES = {}; const ADDRESSES = {}; for (const file of regularTsFiles) { const filePath = path.join(WRAPPERS_DIR, file); const content = fs.readFileSync(filePath, 'utf-8'); const contractName = path.basename(file, '.ts'); // Parse the TypeScript file const sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.ES2015, true); const { ops, errors } = extractStaticProperties(sourceFile, contractName); if (Object.keys(ops).length > 0) { OP_ALIASES[contractName] = ops; } if (Object.keys(errors).length > 0) { ERROR_ALIASES[contractName] = errors; } } // Merge blueprint.config.ts aliases if available if (blueprintAliases) { // Merge codeHashes if (blueprintAliases.codeHashes) { for (const [contractName, codeHash] of Object.entries(blueprintAliases.codeHashes)) { if (typeof codeHash === 'string') { CODES[contractName] = codeHash.replace('0x', ''); } } } // Merge addresses if (blueprintAliases.addresses) { for (const [contractName, address] of Object.entries(blueprintAliases.addresses)) { if (typeof address === 'string') { ADDRESSES[address] = contractName; } } } // Add opcodes as common if (blueprintAliases.opcodes) { // Add to special contract called Common if (!OP_ALIASES['BlueprintCommon']) { OP_ALIASES['BlueprintCommon'] = {}; } for (const [opName, opCode] of Object.entries(blueprintAliases.opcodes)) { OP_ALIASES['BlueprintCommon'][opName] = opCode; } } // Add errorCodes as common too if (blueprintAliases.errorCodes) { // Add to special contract called Common if (!ERROR_ALIASES['BlueprintCommon']) { ERROR_ALIASES['BlueprintCommon'] = {}; } for (const [errorName, errorCode] of Object.entries(blueprintAliases.errorCodes)) { ERROR_ALIASES['BlueprintCommon'][errorName] = errorCode; } } } return { CODES, ADDRESSES, OP_ALIASES, ERROR_ALIASES }; } // Function to find alias for a given action function findActionAlias(action, opAliases) { for (const [contractName, ops] of Object.entries(opAliases)) { for (const [aliasName, opValue] of Object.entries(ops)) { if (opValue === action) { return { contract: contractName, alias: aliasName }; } } } return null; } // Function to find error alias function findErrorAlias(errorCode, errorAliases) { for (const [contractName, errors] of Object.entries(errorAliases)) { for (const [aliasName, errorValue] of Object.entries(errors)) { if (errorValue === errorCode) { return { contract: contractName, alias: aliasName }; } } } return null; }