@fuel-infrastructure/fuel-hyperlane-registry
Version:
A collection of configs, artifacts, and schemas for Hyperlane
173 lines (172 loc) • 7.32 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { parse as yamlParse } from 'yaml';
import { CHAIN_FILE_REGEX, SCHEMA_REF, WARP_ROUTE_CONFIG_FILE_REGEX, WARP_ROUTE_DEPLOY_FILE_REGEX, } from '../consts.js';
import { ChainAddressesSchema } from '../types.js';
import { toYamlString } from '../utils.js';
import { RegistryType, } from './IRegistry.js';
import { SynchronousRegistry } from './SynchronousRegistry.js';
import { warpRouteConfigPathToId, warpRouteDeployConfigPathToId } from './warp-utils.js';
/**
* A registry that uses a local file system path as its data source.
* Requires file system access so it cannot be used in the browser.
*/
export class FileSystemRegistry extends SynchronousRegistry {
type = RegistryType.FileSystem;
constructor(options) {
super(options);
}
getUri(itemPath) {
if (!itemPath)
return super.getUri();
return path.join(this.uri, itemPath);
}
/**
* Retrieves filepaths for chains, warp core, and warp deploy configs
*/
listRegistryContent() {
if (this.listContentCache)
return this.listContentCache;
const chainFileList = this.listFiles(path.join(this.uri, this.getChainsPath()));
const chains = {};
for (const filePath of chainFileList) {
const matches = filePath.match(CHAIN_FILE_REGEX);
if (!matches)
continue;
const [_, chainName, fileName] = matches;
chains[chainName] ??= {};
// @ts-ignore allow dynamic key assignment
chains[chainName][fileName] = filePath;
}
const warpRoutes = {};
const warpRouteFiles = this.listFiles(path.join(this.uri, this.getWarpRoutesPath()));
for (const filePath of warpRouteFiles) {
if (!WARP_ROUTE_CONFIG_FILE_REGEX.test(filePath))
continue;
const routeId = warpRouteConfigPathToId(filePath);
warpRoutes[routeId] = filePath;
}
const warpDeployConfig = {};
const warpDeployFiles = this.listFiles(path.join(this.uri, this.getWarpRoutesPath()));
for (const filePath of warpDeployFiles) {
if (!WARP_ROUTE_DEPLOY_FILE_REGEX.test(filePath))
continue;
const routeId = warpRouteDeployConfigPathToId(filePath);
warpDeployConfig[routeId] = filePath;
}
return (this.listContentCache = { chains, deployments: { warpRoutes, warpDeployConfig } });
}
getMetadata() {
if (this.metadataCache)
return this.metadataCache;
const chainMetadata = {};
const repoContents = this.listRegistryContent();
for (const [chainName, chainFiles] of Object.entries(repoContents.chains)) {
if (!chainFiles.metadata)
continue;
const data = fs.readFileSync(chainFiles.metadata, 'utf8');
chainMetadata[chainName] = yamlParse(data);
}
return (this.metadataCache = chainMetadata);
}
getAddresses() {
if (this.addressCache)
return this.addressCache;
const chainAddresses = {};
const repoContents = this.listRegistryContent();
for (const [chainName, chainFiles] of Object.entries(repoContents.chains)) {
if (!chainFiles.addresses)
continue;
const data = fs.readFileSync(chainFiles.addresses, 'utf8');
chainAddresses[chainName] = ChainAddressesSchema.parse(yamlParse(data));
}
return (this.addressCache = chainAddresses);
}
removeChain(chainName) {
const chainFiles = this.listRegistryContent().chains[chainName];
super.removeChain(chainName);
this.removeFiles(Object.values(chainFiles));
}
addWarpRoute(config, options) {
let { configPath } = this.getWarpRoutesArtifactPaths(config, options);
configPath = path.join(this.uri, configPath);
this.createFile({ filePath: configPath, data: toYamlString(config, SCHEMA_REF) });
}
addWarpRouteConfig(warpConfig, fileName) {
const filePath = path.join(this.uri, this.getWarpRoutesPath(), fileName);
this.createFile({ filePath, data: toYamlString(warpConfig) });
}
listFiles(dirPath) {
if (!fs.existsSync(dirPath))
return [];
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
const filePaths = entries.map((entry) => {
const fullPath = path.join(dirPath, entry.name);
return entry.isDirectory() ? this.listFiles(fullPath) : fullPath;
});
return filePaths.flat();
}
createOrUpdateChain(chain) {
if (!chain.metadata && !chain.addresses)
throw new Error(`Chain ${chain.chainName} must have metadata or addresses, preferably both`);
const currentChains = this.listRegistryContent();
if (!currentChains.chains[chain.chainName]) {
this.logger.debug(`Chain ${chain.chainName} not found in registry, adding it now`);
}
if (chain.metadata) {
this.createChainFile(chain.chainName, 'metadata', chain.metadata, this.getMetadata(), SCHEMA_REF);
}
if (chain.addresses) {
this.createChainFile(chain.chainName, 'addresses', chain.addresses, this.getAddresses());
}
}
createChainFile(chainName, fileName, data, cache, prefix) {
const filePath = path.join(this.uri, this.getChainsPath(), chainName, `${fileName}.yaml`);
const currentChains = this.listRegistryContent().chains;
currentChains[chainName] ||= {};
currentChains[chainName][fileName] = filePath;
cache[chainName] = data;
this.createFile({ filePath, data: toYamlString(data, prefix) });
}
createFile(file) {
const dirPath = path.dirname(file.filePath);
if (!fs.existsSync(dirPath))
fs.mkdirSync(dirPath, {
recursive: true,
});
fs.writeFileSync(file.filePath, file.data);
}
removeFiles(filePaths) {
for (const filePath of filePaths) {
fs.unlinkSync(filePath);
}
const parentDir = path.dirname(filePaths[0]);
if (fs.readdirSync(parentDir).length === 0) {
fs.rmdirSync(parentDir);
}
}
getWarpRoutesForIds(ids) {
const warpRoutes = this.listRegistryContent().deployments.warpRoutes;
return this.readConfigsForIds(ids, warpRoutes);
}
getWarpDeployConfigForIds(ids) {
const warpDeployConfig = this.listRegistryContent().deployments.warpDeployConfig;
return this.readConfigsForIds(ids, warpDeployConfig);
}
/**
* Reads config files for the given WarpRouteIds.
* @param ids - The WarpRouteIds to read configs for.
* @param configURIs - A mapping of WarpRouteIds to file paths where the configs are stored.
* @returns An array of config objects.
*/
readConfigsForIds(ids, configURIs) {
const configs = [];
for (const [id, filePath] of Object.entries(configURIs)) {
if (!ids.includes(id))
continue;
const data = fs.readFileSync(filePath, 'utf8');
configs.push(yamlParse(data));
}
return configs;
}
}