@factorial-finance/blueprint-node
Version:
blueprint-node-plugin
349 lines (348 loc) • 14.6 kB
JavaScript
;
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;
}