mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
feat: add versioning utilities and helpers
Add TypeScript utilities: getContractVersion(), getVersionsMatrix(), validateVersionChecksum(), getAnvilRpcUrl(), getDependencyVersions(). Integrate version validation into deployment flow.
This commit is contained in:
parent
815036624b
commit
5c1fde6ff2
7 changed files with 419 additions and 8 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { $ } from "bun";
|
||||
import { CHAIN_CONFIGS, loadChainConfig } from "configs/contracts/config";
|
||||
import invariant from "tiny-invariant";
|
||||
|
|
@ -45,6 +47,7 @@ export const validateDeploymentParams = (options: ContractDeploymentOptions) =>
|
|||
*/
|
||||
export const buildContracts = async () => {
|
||||
logger.info("🛳️ Building contracts...");
|
||||
|
||||
const {
|
||||
exitCode: buildExitCode,
|
||||
stderr: buildStderr,
|
||||
|
|
@ -116,6 +119,59 @@ export const executeDeployment = async (
|
|||
logger.success("Contracts deployed successfully");
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the current code version from contracts/VERSION file
|
||||
* This is the single source of truth for the code version
|
||||
*/
|
||||
export const getCurrentVersion = async (): Promise<string> => {
|
||||
const cwd = process.cwd();
|
||||
const repoRoot = path.basename(cwd) === "test" ? path.join(cwd, "..") : cwd;
|
||||
const versionFile = path.join(repoRoot, "contracts", "VERSION");
|
||||
|
||||
try {
|
||||
const version = readFileSync(versionFile, "utf8").trim();
|
||||
if (!version) {
|
||||
throw new Error("VERSION file is empty");
|
||||
}
|
||||
return version;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read contracts/VERSION: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates versions-matrix.json to track deployment
|
||||
* Does NOT bump version - version comes from contracts/VERSION file
|
||||
*/
|
||||
export const updateDeploymentTracking = async (chain: string | undefined, version: string) => {
|
||||
if (!chain) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const cwd = process.cwd();
|
||||
const repoRoot = path.basename(cwd) === "test" ? path.join(cwd, "..") : cwd;
|
||||
const matrixFile = path.join(repoRoot, "contracts", "versions-matrix.json");
|
||||
|
||||
const matrixContent = readFileSync(matrixFile, "utf8");
|
||||
const matrix = JSON.parse(matrixContent);
|
||||
|
||||
// Update deployment info
|
||||
if (!matrix.deployments) {
|
||||
matrix.deployments = {};
|
||||
}
|
||||
matrix.deployments[chain] = {
|
||||
version,
|
||||
lastDeployed: new Date().toISOString()
|
||||
};
|
||||
|
||||
writeFileSync(matrixFile, JSON.stringify(matrix, null, 2));
|
||||
logger.info(`📝 Updated versions-matrix.json: ${chain} deployed at version ${version}`);
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Could not update versions-matrix.json: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the parameters from the deployed contracts and add it to the collection.
|
||||
*/
|
||||
|
|
@ -224,16 +280,25 @@ export const deployContracts = async (options: {
|
|||
// Build contracts
|
||||
await buildContracts();
|
||||
|
||||
// Get current code version from VERSION file (single source of truth)
|
||||
const currentVersion = await getCurrentVersion();
|
||||
logger.info(`📌 Deploying code version: ${currentVersion}`);
|
||||
|
||||
// Construct and execute deployment
|
||||
const deployCommand = constructDeployCommand(deploymentOptions);
|
||||
const env = buildDeploymentEnv(deploymentOptions);
|
||||
const env = buildDeploymentEnv(deploymentOptions, currentVersion);
|
||||
await executeDeployment(deployCommand, undefined, networkId, env);
|
||||
|
||||
if (!txExecutionEnabled) {
|
||||
await emitOwnerTransactionCalldata(networkId);
|
||||
}
|
||||
|
||||
logger.success(`DataHaven contracts deployed successfully to ${networkId}`);
|
||||
// Update versions-matrix.json to track this deployment
|
||||
await updateDeploymentTracking(options.chain, currentVersion);
|
||||
|
||||
logger.success(
|
||||
`DataHaven contracts deployed successfully to ${options.chain} at version ${currentVersion}`
|
||||
);
|
||||
};
|
||||
|
||||
const normalizePrivateKey = (key?: string): `0x${string}` | undefined => {
|
||||
|
|
@ -243,7 +308,7 @@ const normalizePrivateKey = (key?: string): `0x${string}` | undefined => {
|
|||
return (key.startsWith("0x") ? key : `0x${key}`) as `0x${string}`;
|
||||
};
|
||||
|
||||
const buildDeploymentEnv = (options: ContractDeploymentOptions) => {
|
||||
const buildDeploymentEnv = (options: ContractDeploymentOptions, version: string) => {
|
||||
const env: Record<string, string> = {};
|
||||
|
||||
if (options.privateKey) {
|
||||
|
|
@ -262,6 +327,9 @@ const buildDeploymentEnv = (options: ContractDeploymentOptions) => {
|
|||
env.TX_EXECUTION = options.txExecution ? "true" : "false";
|
||||
}
|
||||
|
||||
// Pass version to Solidity scripts for contract initialization
|
||||
env.DATAHAVEN_VERSION = version;
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
|
|
@ -337,12 +405,12 @@ if (import.meta.main) {
|
|||
}
|
||||
|
||||
if (!options.rpcUrl) {
|
||||
console.error("Error: --rpc-url parameter is required");
|
||||
logger.error("Error: --rpc-url parameter is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.verified && !options.blockscoutBackendUrl) {
|
||||
console.error("Error: --blockscout-url parameter is required when using --verified");
|
||||
logger.error("Error: --blockscout-url parameter is required when using --verified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const setDataHavenParameters = async (
|
|||
dhApi.tx.Parameters.set_parameter({
|
||||
key_value: {
|
||||
type: "RuntimeConfig",
|
||||
value: { type: p.name, value: [p.value] }
|
||||
value: { type: p.name as any, value: [p.value] }
|
||||
}
|
||||
}).decodedCall
|
||||
);
|
||||
|
|
|
|||
48
test/utils/anvil.ts
Normal file
48
test/utils/anvil.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { getPortFromKurtosis } from "./kurtosis";
|
||||
import { logger } from "./logger";
|
||||
|
||||
/**
|
||||
* Gets the RPC URL for the Anvil (local Ethereum) node running in Kurtosis
|
||||
* @param enclaveName - The name of the Kurtosis enclave (default: "datahaven-ethereum")
|
||||
* @returns The HTTP RPC URL for the Ethereum node
|
||||
*/
|
||||
export const getAnvilRpcUrl = async (enclaveName = "datahaven-ethereum"): Promise<string> => {
|
||||
try {
|
||||
logger.debug("Getting Anvil RPC URL from Kurtosis...");
|
||||
|
||||
// Get the RPC port from the EL (Execution Layer) service
|
||||
const rpcPort = await getPortFromKurtosis("el-1-reth-lodestar", "rpc", enclaveName);
|
||||
|
||||
const rpcUrl = `http://127.0.0.1:${rpcPort}`;
|
||||
logger.debug(`Anvil RPC URL: ${rpcUrl}`);
|
||||
|
||||
return rpcUrl;
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Failed to get Anvil RPC URL from Kurtosis: ${error}`);
|
||||
logger.warn(" Falling back to default http://localhost:8545");
|
||||
return "http://localhost:8545";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the WebSocket URL for the Anvil (local Ethereum) node running in Kurtosis
|
||||
* @param enclaveName - The name of the Kurtosis enclave (default: "datahaven-ethereum")
|
||||
* @returns The WebSocket URL for the Ethereum node
|
||||
*/
|
||||
export const getAnvilWsUrl = async (enclaveName = "datahaven-ethereum"): Promise<string> => {
|
||||
try {
|
||||
logger.debug("Getting Anvil WebSocket URL from Kurtosis...");
|
||||
|
||||
// Get the WS port from the EL (Execution Layer) service
|
||||
const wsPort = await getPortFromKurtosis("el-1-reth-lodestar", "ws", enclaveName);
|
||||
|
||||
const wsUrl = `ws://127.0.0.1:${wsPort}`;
|
||||
logger.debug(`Anvil WebSocket URL: ${wsUrl}`);
|
||||
|
||||
return wsUrl;
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Failed to get Anvil WebSocket URL from Kurtosis: ${error}`);
|
||||
logger.warn(" Falling back to default ws://localhost:8546");
|
||||
return "ws://localhost:8546";
|
||||
}
|
||||
};
|
||||
|
|
@ -11,6 +11,7 @@ const ethAddressCustom = z.custom<`0x${string}`>(
|
|||
(val) => typeof val === "string" && ethAddressRegex.test(val),
|
||||
{ message: "Invalid Ethereum address" }
|
||||
);
|
||||
|
||||
const DeployedStrategySchema = z.object({
|
||||
address: ethAddress,
|
||||
underlyingToken: ethAddress,
|
||||
|
|
@ -24,6 +25,7 @@ const DeploymentsSchema = z.object({
|
|||
Gateway: ethAddressCustom,
|
||||
ServiceManager: ethAddressCustom,
|
||||
ServiceManagerImplementation: ethAddressCustom,
|
||||
RewardsAgent: ethAddressCustom,
|
||||
DelegationManager: ethAddressCustom,
|
||||
StrategyManager: ethAddressCustom,
|
||||
AVSDirectory: ethAddressCustom,
|
||||
|
|
@ -34,6 +36,25 @@ const DeploymentsSchema = z.object({
|
|||
PermissionController: ethAddressCustom,
|
||||
ETHPOSDeposit: ethAddressCustom.optional(),
|
||||
BaseStrategyImplementation: ethAddressCustom.optional(),
|
||||
ProxyAdmin: ethAddressCustom.optional(),
|
||||
// Version tag for this set of deployed contracts (optional for backwards compatibility)
|
||||
version: z.string().optional(),
|
||||
deps: z
|
||||
.object({
|
||||
eigenlayer: z
|
||||
.object({
|
||||
release: z.string().optional(),
|
||||
gitCommit: z.string().optional()
|
||||
})
|
||||
.optional(),
|
||||
snowbridge: z
|
||||
.object({
|
||||
release: z.string().optional(),
|
||||
gitCommit: z.string().optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.optional(),
|
||||
DeployedStrategies: z.array(DeployedStrategySchema).optional()
|
||||
});
|
||||
|
||||
|
|
@ -52,7 +73,7 @@ export const parseDeploymentsFile = async (networkId = "anvil"): Promise<Deploym
|
|||
throw new Error(`Error reading ${networkId} deployments file`);
|
||||
}
|
||||
const deploymentsJson = await deploymentsFile.json();
|
||||
logger.info(`Deployments: ${JSON.stringify(deploymentsJson, null, 2)}`);
|
||||
logger.debug(`Deployments: ${JSON.stringify(deploymentsJson, null, 2)}`);
|
||||
try {
|
||||
const parsedDeployments = DeploymentsSchema.parse(deploymentsJson);
|
||||
logger.debug(`Successfully parsed ${networkId} deployments file.`);
|
||||
|
|
@ -70,6 +91,7 @@ const abiMap = {
|
|||
Gateway: generated.gatewayAbi,
|
||||
ServiceManager: generated.dataHavenServiceManagerAbi,
|
||||
ServiceManagerImplementation: generated.dataHavenServiceManagerAbi,
|
||||
RewardsAgent: generated.agentAbi,
|
||||
DelegationManager: generated.delegationManagerAbi,
|
||||
StrategyManager: generated.strategyManagerAbi,
|
||||
AVSDirectory: generated.avsDirectoryAbi,
|
||||
|
|
@ -81,7 +103,10 @@ const abiMap = {
|
|||
ETHPOSDeposit: generated.iethposDepositAbi,
|
||||
BaseStrategyImplementation: generated.strategyBaseTvlLimitsAbi,
|
||||
DeployedStrategies: erc20Abi
|
||||
} as const satisfies Record<keyof Omit<Deployments, "network">, Abi>;
|
||||
} as const satisfies Record<
|
||||
keyof Omit<Deployments, "network" | "ProxyAdmin" | "version" | "deps">,
|
||||
Abi
|
||||
>;
|
||||
|
||||
type ContractName = keyof typeof abiMap;
|
||||
type AbiFor<C extends ContractName> = (typeof abiMap)[C];
|
||||
|
|
@ -95,6 +120,10 @@ export const getContractInstance = async <C extends ContractName>(
|
|||
viemClient?: ViemClientInterface,
|
||||
network = "anvil"
|
||||
) => {
|
||||
invariant(
|
||||
contract !== "DeployedStrategies",
|
||||
"getContractInstance does not support 'DeployedStrategies' as it is an array. Use a different method to access deployed strategies."
|
||||
);
|
||||
const deployments = await parseDeploymentsFile(network);
|
||||
const contractAddress = deployments[contract];
|
||||
logger.debug(`Contract ${contract} deployed to ${contractAddress}`);
|
||||
|
|
|
|||
205
test/utils/contracts/versioning.ts
Normal file
205
test/utils/contracts/versioning.ts
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { CHAIN_CONFIGS } from "configs/contracts/config";
|
||||
import { logger } from "utils";
|
||||
import { getContractInstance } from "utils/contracts";
|
||||
import type { ViemClientInterface } from "utils/viem";
|
||||
import { createWalletClient, defineChain, http, publicActions } from "viem";
|
||||
|
||||
export interface ContractVersionCheckResult {
|
||||
ok: boolean;
|
||||
skipped: boolean;
|
||||
}
|
||||
|
||||
const assertValidChain = (chain: string) => {
|
||||
const supportedChains = ["hoodi", "ethereum", "anvil"];
|
||||
if (!supportedChains.includes(chain)) {
|
||||
throw new Error(`Unsupported chain: ${chain}. Supported chains: ${supportedChains.join(", ")}`);
|
||||
}
|
||||
};
|
||||
|
||||
const isInfraUnavailableError = (error: unknown): boolean => {
|
||||
const message =
|
||||
error instanceof Error ? error.message : typeof error === "string" ? error : String(error);
|
||||
|
||||
return (
|
||||
message.includes("Failed to connect to Docker daemon") ||
|
||||
(message.includes("container") &&
|
||||
message.includes("cannot be found in running container list")) ||
|
||||
message.includes("ECONNREFUSED") ||
|
||||
message.includes("ECONNRESET") ||
|
||||
message.includes("ENOTFOUND") ||
|
||||
message.includes("EHOSTUNREACH") ||
|
||||
message.includes("Was there a typo in the url or port?")
|
||||
);
|
||||
};
|
||||
|
||||
export const checkContractVersions = async (
|
||||
chain: string,
|
||||
rpcUrl?: string
|
||||
): Promise<ContractVersionCheckResult> => {
|
||||
assertValidChain(chain);
|
||||
logger.info(`🔍 Checking contract versions for chain '${chain}'`);
|
||||
|
||||
// Read version from versions-matrix.json
|
||||
const cwd = process.cwd();
|
||||
const repoRoot = path.basename(cwd) === "test" ? path.join(cwd, "..") : cwd;
|
||||
const matrixFile = path.join(repoRoot, "contracts", "versions-matrix.json");
|
||||
|
||||
let version: string | undefined;
|
||||
try {
|
||||
const matrixContent = readFileSync(matrixFile, "utf8");
|
||||
const matrix = JSON.parse(matrixContent);
|
||||
version = matrix.deployments?.[chain]?.version;
|
||||
} catch (_error) {
|
||||
logger.info(
|
||||
"ℹ️ Could not read versions-matrix.json - skipping version check (probably fresh deployment)"
|
||||
);
|
||||
return { ok: true, skipped: true };
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
logger.info(
|
||||
`ℹ️ No version tracked for '${chain}' in versions-matrix.json - skipping version check (probably fresh deployment)`
|
||||
);
|
||||
return { ok: true, skipped: true };
|
||||
}
|
||||
|
||||
let viemClient: ViemClientInterface | undefined;
|
||||
const chainConfig = CHAIN_CONFIGS[chain as keyof typeof CHAIN_CONFIGS];
|
||||
if (chainConfig && chain !== "anvil") {
|
||||
const chainDef = defineChain({
|
||||
id: chainConfig.CHAIN_ID,
|
||||
name: chainConfig.NETWORK_NAME,
|
||||
nativeCurrency: {
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18
|
||||
},
|
||||
rpcUrls: {
|
||||
default: {
|
||||
http: [rpcUrl ?? chainConfig.RPC_URL]
|
||||
}
|
||||
},
|
||||
blockExplorers: chainConfig.BLOCK_EXPLORER
|
||||
? {
|
||||
default: { name: "Explorer", url: chainConfig.BLOCK_EXPLORER }
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
||||
viemClient = createWalletClient({
|
||||
chain: chainDef,
|
||||
transport: http()
|
||||
}).extend(publicActions) as unknown as ViemClientInterface;
|
||||
}
|
||||
|
||||
let ok = true;
|
||||
|
||||
try {
|
||||
const serviceManager: any = await getContractInstance("ServiceManager", viemClient, chain);
|
||||
const smVersion: string = await serviceManager.read.DATAHAVEN_VERSION();
|
||||
|
||||
if (smVersion !== version) {
|
||||
logger.error(
|
||||
`❌ DataHavenServiceManager DATAHAVEN_VERSION=${smVersion} does not match deployments version=${version} for chain='${chain}'.`
|
||||
);
|
||||
ok = false;
|
||||
} else {
|
||||
logger.info(
|
||||
`✅ DataHavenServiceManager version matches deployments version (${version}) for chain='${chain}'.`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isInfraUnavailableError(error)) {
|
||||
logger.warn(
|
||||
`⚠️ Skipping on-chain version checks for chain='${chain}': no local Ethereum node or containers detected (${error}).`
|
||||
);
|
||||
return { ok: true, skipped: true };
|
||||
}
|
||||
const errorMsg = String(error);
|
||||
|
||||
// Check if function doesn't exist (old deployment without version tracking)
|
||||
if (
|
||||
errorMsg.includes("DATAHAVEN_VERSION") &&
|
||||
(errorMsg.includes("returned no data") || errorMsg.includes("does not have the function"))
|
||||
) {
|
||||
logger.warn(
|
||||
`⚠️ ServiceManager at ${chain} does not have DATAHAVEN_VERSION() function yet (old deployment). Skipping on-chain version check.`
|
||||
);
|
||||
return { ok: true, skipped: true };
|
||||
}
|
||||
|
||||
if (
|
||||
errorMsg.includes("DATAHAVEN_VERSION") &&
|
||||
(errorMsg.includes("reverted") || errorMsg.includes("missing revert data"))
|
||||
) {
|
||||
throw new Error(
|
||||
`ServiceManager at ${chain} does not expose DATAHAVEN_VERSION() yet. ` +
|
||||
"This usually means the on-chain implementation is older than the versioning update. " +
|
||||
"Upgrade the ServiceManager implementation, then re-run the check."
|
||||
);
|
||||
}
|
||||
throw new Error(`Failed to read version from DataHavenServiceManager: ${error}`);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
return { ok: false, skipped: false };
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`✅ All checked contract versions match deployments version=${version} on '${chain}'.`
|
||||
);
|
||||
return { ok: true, skipped: false };
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that a version string follows semantic versioning (X.Y.Z)
|
||||
*/
|
||||
export const isValidSemver = (version: string): boolean => {
|
||||
const semverRegex = /^\d+\.\d+\.\d+$/;
|
||||
return semverRegex.test(version);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates version formats across all deployment files
|
||||
*/
|
||||
export const validateVersionFormats = async (): Promise<boolean> => {
|
||||
const cwd = process.cwd();
|
||||
const repoRoot = path.basename(cwd) === "test" ? path.join(cwd, "..") : cwd;
|
||||
const matrixFile = path.join(repoRoot, "contracts", "versions-matrix.json");
|
||||
|
||||
let allValid = true;
|
||||
|
||||
try {
|
||||
const matrixContent = readFileSync(matrixFile, "utf8");
|
||||
const matrix = JSON.parse(matrixContent);
|
||||
const codeVersion = matrix?.code?.version;
|
||||
|
||||
if (!codeVersion) {
|
||||
logger.warn("⚠️ versions-matrix.json has no code.version");
|
||||
allValid = false;
|
||||
} else if (!isValidSemver(codeVersion)) {
|
||||
logger.error(`❌ Invalid code.version format in versions-matrix.json: ${codeVersion}`);
|
||||
allValid = false;
|
||||
}
|
||||
|
||||
const deployments = matrix?.deployments ?? {};
|
||||
for (const [chain, entry] of Object.entries(deployments)) {
|
||||
const version = (entry as { version?: string }).version;
|
||||
if (!version) {
|
||||
logger.warn(`⚠️ No version for '${chain}' in versions-matrix.json deployments`);
|
||||
continue;
|
||||
}
|
||||
if (!isValidSemver(version)) {
|
||||
logger.error(`❌ Invalid deployment version format for '${chain}': ${version}`);
|
||||
allValid = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Could not read versions-matrix.json: ${error}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return allValid;
|
||||
};
|
||||
59
test/utils/dependencyVersions.ts
Normal file
59
test/utils/dependencyVersions.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import path from "node:path";
|
||||
import { $ } from "bun";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export interface DependencyInfo {
|
||||
release?: string;
|
||||
gitCommit: string;
|
||||
}
|
||||
|
||||
export interface DependencyVersions {
|
||||
eigenlayer: DependencyInfo;
|
||||
snowbridge: DependencyInfo;
|
||||
}
|
||||
|
||||
const resolveRepoRoot = (): string => {
|
||||
const cwd = process.cwd();
|
||||
const base = path.basename(cwd);
|
||||
return base === "test" ? path.join(cwd, "..") : cwd;
|
||||
};
|
||||
|
||||
const getGitInfo = async (relativePath: string): Promise<DependencyInfo> => {
|
||||
const repoRoot = resolveRepoRoot();
|
||||
const fullPath = path.join(repoRoot, relativePath);
|
||||
|
||||
const { stdout: shaOut, exitCode: shaCode } = await $`git -C ${fullPath} rev-parse HEAD`
|
||||
.nothrow()
|
||||
.quiet();
|
||||
|
||||
if (shaCode !== 0) {
|
||||
throw new Error(`Failed to resolve git commit for ${relativePath}`);
|
||||
}
|
||||
|
||||
const gitCommit = shaOut.toString().trim();
|
||||
|
||||
const { stdout: tagOut, exitCode: tagCode } =
|
||||
await $`git -C ${fullPath} describe --tags --exact-match`.nothrow().quiet();
|
||||
|
||||
const release = tagCode === 0 ? tagOut.toString().trim() : undefined;
|
||||
|
||||
return { gitCommit, release };
|
||||
};
|
||||
|
||||
export const getDependencyVersions = async (): Promise<DependencyVersions> => {
|
||||
try {
|
||||
const [eigenlayer, snowbridge] = await Promise.all([
|
||||
getGitInfo(path.join("contracts", "lib", "eigenlayer-contracts")),
|
||||
getGitInfo(path.join("contracts", "lib", "snowbridge"))
|
||||
]);
|
||||
|
||||
logger.info(
|
||||
`Derived dependency versions: eigenlayer=${eigenlayer.release ?? eigenlayer.gitCommit}, snowbridge=${snowbridge.release ?? snowbridge.gitCommit}`
|
||||
);
|
||||
|
||||
return { eigenlayer, snowbridge };
|
||||
} catch (error) {
|
||||
logger.error(`Failed to derive dependency versions from git: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
export * from "./anvil";
|
||||
export * from "./blockscout";
|
||||
export * from "./constants";
|
||||
export * from "./contracts";
|
||||
export * from "./contracts/versioning";
|
||||
export * from "./docker";
|
||||
export * from "./events";
|
||||
export * from "./input";
|
||||
|
|
|
|||
Loading…
Reference in a new issue