import { GenericContainer, PullPolicy, type StartedTestContainer, Wait, } from 'testcontainers' import * as Instance from '../Instance.js' import { command, type tempo as core_tempo } from '../instances/tempo.js' import * as ContainerOptions from './containerOptions.js' export type { Instance, InstanceOptions } from '../Instance.js' /** * Defines a Tempo instance. * * @example * ```ts * const instance = Instance.tempo({ port: 8545 }) * await instance.start() * // ... * await instance.stop() * ``` */ export const tempo = Instance.define((parameters?: tempo.Parameters) => { const { containerName = `tempo.${crypto.randomUUID()}`, image = 'ghcr.io/tempoxyz/tempo:latest', log: log_, startupTimeout, ...args } = parameters || {} const log = (() => { try { return JSON.parse(log_ as string) } catch { return log_ } })() const RUST_LOG = log && typeof log !== 'boolean' ? log : '' const name = 'tempo' let container: StartedTestContainer | undefined return { _internal: { args, }, host: args.host ?? 'localhost', name, port: args.port ?? 8545, async start({ port = args.port }, { emitter, setEndpoint }) { const promise = Promise.withResolvers() const containerPort = port ?? 8545 const c = new GenericContainer(image) .withPullPolicy(PullPolicy.alwaysPull()) .withPlatform('linux/x86_64') .withExposedPorts(containerPort) .withExtraHosts([ { host: 'host.docker.internal', ipAddress: 'host-gateway' }, ]) .withName(containerName) .withEnvironment({ RUST_LOG }) .withCommand(command({ ...args, port: containerPort })) .withWaitStrategy( Wait.forLogMessage( /Received (block|new payload) from consensus engine/, ), ) .withLogConsumer((stream) => { stream.on('data', (data) => { const message = data.toString() emitter.emit('message', message) emitter.emit('stdout', message) if (log) console.log(message) if (message.includes('shutting down')) promise.reject(new Error(`Failed to start: ${message}`)) }) stream.on('error', (error) => { if (log) console.error(error.message) emitter.emit('message', error.message) emitter.emit('stderr', error.message) promise.reject(new Error(`Failed to start: ${error.message}`)) }) }) .withStartupTimeout( ContainerOptions.resolveStartupTimeout(startupTimeout), ) c.start() .then((started) => { container = started setEndpoint?.({ host: started.getHost(), port: started.getMappedPort(containerPort), }) promise.resolve() }) .catch(promise.reject) return promise.promise }, async stop() { if (!container) return await container.stop() container = undefined }, } }) export declare namespace tempo { export type Parameters = Omit & ContainerOptions.Parameters & { /** * Name of the container. */ containerName?: string | undefined /** * Docker image to use. */ image?: string | undefined /** * Host the server will listen on. */ host?: string | undefined /** * Port the server will listen on. */ port?: number | undefined } }