306 lines
9.7 KiB
TypeScript
306 lines
9.7 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Builds Docker images for Node.js, Deno, and CLI e2e tests.
|
|
*
|
|
* In CI mode (COMPOSIO_E2E_NODE_VERSION or COMPOSIO_E2E_DENO_VERSION or COMPOSIO_E2E_CLI_VERSION set):
|
|
* Only builds the image for the specified version(s).
|
|
*
|
|
* In local mode:
|
|
* Builds images for all versions in WELL_KNOWN_NODE_VERSIONS and WELL_KNOWN_DENO_VERSIONS.
|
|
*/
|
|
|
|
import { $ } from 'bun';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { readFileSync } from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { WELL_KNOWN_NODE_VERSIONS, WELL_KNOWN_DENO_VERSIONS } from '../src/const';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
type DockerToolchain = {
|
|
nodeVersion: string;
|
|
nodeMajorVersion: string;
|
|
};
|
|
|
|
function getRepoRoot(): string {
|
|
// From scripts/ to repo root: scripts -> _utils -> e2e-tests -> ts -> composio
|
|
return resolve(__dirname, '../../../..');
|
|
}
|
|
|
|
// bun and pnpm are installed inside the images from mise.toml/mise.lock, so only the
|
|
// matrixed runtimes (node, deno) need to be resolved on the host and threaded as build args.
|
|
function getMiseVersion(repoRoot: string, tool: 'node' | 'deno'): string {
|
|
try {
|
|
return execFileSync('mise', ['current', tool], { cwd: repoRoot, encoding: 'utf-8' }).trim();
|
|
} catch (err) {
|
|
throw new Error(`Failed to resolve ${tool} version from mise.toml: ${(err as Error).message}`);
|
|
}
|
|
}
|
|
|
|
function getMajorVersion(version: string): string {
|
|
return version.split('.')[0] ?? version;
|
|
}
|
|
|
|
function getDockerToolchain(repoRoot: string): DockerToolchain {
|
|
const nodeVersion = getMiseVersion(repoRoot, 'node');
|
|
return {
|
|
nodeVersion,
|
|
nodeMajorVersion: getMajorVersion(nodeVersion),
|
|
};
|
|
}
|
|
|
|
function getCliPackageVersion(repoRoot: string): string {
|
|
const pkg = readFileSync(resolve(repoRoot, 'ts/packages/cli/package.json'), 'utf-8');
|
|
const parsed = JSON.parse(pkg) as { version?: string };
|
|
if (!parsed.version) {
|
|
throw new Error('Missing version in ts/packages/cli/package.json');
|
|
}
|
|
return String(parsed.version).trim();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Node.js image building
|
|
// ============================================================================
|
|
|
|
function imageTagForNodeVersion(nodeVersion: string): string {
|
|
return `composio-e2e-node:${nodeVersion}`;
|
|
}
|
|
|
|
function defaultNodeLabels(nodeVersion: string): Record<string, string> {
|
|
return {
|
|
'composio.e2e': 'true',
|
|
'composio.runtime': 'node',
|
|
'composio.node_version': nodeVersion,
|
|
};
|
|
}
|
|
|
|
async function buildNodeImage(nodeVersion: string, repoRoot: string): Promise<boolean> {
|
|
const dockerfilePath = resolve(repoRoot, 'ts/e2e-tests/_utils/Dockerfile.node');
|
|
const imageTag = imageTagForNodeVersion(nodeVersion);
|
|
const labels = defaultNodeLabels(nodeVersion);
|
|
const labelArgs = Object.entries(labels)
|
|
.map(([k, v]) => `--label=${k}=${v}`)
|
|
.join(' ');
|
|
|
|
console.log(`\nBuilding image for Node.js ${nodeVersion}...`);
|
|
|
|
const result =
|
|
await $`docker build -f ${dockerfilePath} --build-arg NODE_VERSION=${nodeVersion} ${{ raw: labelArgs }} -t ${imageTag} ${repoRoot}`
|
|
.cwd(repoRoot)
|
|
.nothrow();
|
|
|
|
if (result.exitCode !== 0) {
|
|
console.error(` Failed to build ${imageTag}:`);
|
|
console.error(result.stderr.toString() || result.stdout.toString());
|
|
return false;
|
|
}
|
|
|
|
console.log(` Built ${imageTag}`);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Deno image building
|
|
// ============================================================================
|
|
|
|
function imageTagForDenoVersion(denoVersion: string): string {
|
|
return `composio-e2e-deno:${denoVersion}`;
|
|
}
|
|
|
|
function defaultDenoLabels(denoVersion: string): Record<string, string> {
|
|
return {
|
|
'composio.e2e': 'true',
|
|
'composio.runtime': 'deno',
|
|
'composio.deno_version': denoVersion,
|
|
};
|
|
}
|
|
|
|
async function buildDenoImage(
|
|
denoVersion: string,
|
|
toolchain: DockerToolchain,
|
|
repoRoot: string
|
|
): Promise<boolean> {
|
|
const dockerfilePath = resolve(repoRoot, 'ts/e2e-tests/_utils/Dockerfile.deno');
|
|
const imageTag = imageTagForDenoVersion(denoVersion);
|
|
const labels = defaultDenoLabels(denoVersion);
|
|
const labelArgs = Object.entries(labels)
|
|
.map(([k, v]) => `--label=${k}=${v}`)
|
|
.join(' ');
|
|
|
|
console.log(`\nBuilding image for Deno ${denoVersion}...`);
|
|
|
|
const result =
|
|
await $`docker build -f ${dockerfilePath} --build-arg DENO_VERSION=${denoVersion} --build-arg NODE_MAJOR=${toolchain.nodeMajorVersion} ${{ raw: labelArgs }} -t ${imageTag} ${repoRoot}`
|
|
.cwd(repoRoot)
|
|
.nothrow();
|
|
|
|
if (result.exitCode !== 0) {
|
|
console.error(` Failed to build ${imageTag}:`);
|
|
console.error(result.stderr.toString() || result.stdout.toString());
|
|
return false;
|
|
}
|
|
|
|
console.log(` Built ${imageTag}`);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// CLI image building
|
|
// ============================================================================
|
|
|
|
function imageTagForCliVersion(cliVersion: string): string {
|
|
return `composio-e2e-cli:${cliVersion}`;
|
|
}
|
|
|
|
function defaultCliLabels(cliVersion: string): Record<string, string> {
|
|
return {
|
|
'composio.e2e': 'true',
|
|
'composio.runtime': 'cli',
|
|
'composio.cli_version': cliVersion,
|
|
};
|
|
}
|
|
|
|
async function buildCliImage(
|
|
cliVersion: string,
|
|
toolchain: DockerToolchain,
|
|
repoRoot: string
|
|
): Promise<boolean> {
|
|
const dockerfilePath = resolve(repoRoot, 'ts/e2e-tests/_utils/Dockerfile.cli');
|
|
const imageTag = imageTagForCliVersion(cliVersion);
|
|
const labels = defaultCliLabels(cliVersion);
|
|
const labelArgs = Object.entries(labels)
|
|
.map(([k, v]) => `--label=${k}=${v}`)
|
|
.join(' ');
|
|
|
|
console.log(`\nBuilding image for CLI ${cliVersion}...`);
|
|
|
|
const result =
|
|
await $`docker build -f ${dockerfilePath} --build-arg CLI_VERSION=${cliVersion} --build-arg NODE_VERSION=${toolchain.nodeVersion} ${{ raw: labelArgs }} -t ${imageTag} ${repoRoot}`
|
|
.cwd(repoRoot)
|
|
.nothrow();
|
|
|
|
if (result.exitCode !== 0) {
|
|
console.error(` Failed to build ${imageTag}:`);
|
|
console.error(result.stderr.toString() || result.stdout.toString());
|
|
return false;
|
|
}
|
|
|
|
console.log(` Built ${imageTag}`);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Main
|
|
// ============================================================================
|
|
|
|
async function main() {
|
|
const repoRoot = getRepoRoot();
|
|
const dockerToolchain = getDockerToolchain(repoRoot);
|
|
const currentNodeVersion = dockerToolchain.nodeVersion;
|
|
const currentDenoVersion = getMiseVersion(repoRoot, 'deno');
|
|
const currentCliVersion = getCliPackageVersion(repoRoot);
|
|
const envNodeVersion = Bun.env.COMPOSIO_E2E_NODE_VERSION;
|
|
const envDenoVersion = Bun.env.COMPOSIO_E2E_DENO_VERSION;
|
|
const envCliVersion = Bun.env.COMPOSIO_E2E_CLI_VERSION;
|
|
|
|
const nodeVersions = new Set<string>();
|
|
const denoVersions = new Set<string>();
|
|
const cliVersions = new Set<string>();
|
|
|
|
// Determine which Node.js versions to build
|
|
if (envNodeVersion) {
|
|
nodeVersions.add(envNodeVersion);
|
|
console.log(`Building Docker image for CI matrix Node.js version: ${envNodeVersion}`);
|
|
} else if (!envDenoVersion && !envCliVersion) {
|
|
// Build all Node.js versions only if no specific runtime was requested
|
|
for (const version of WELL_KNOWN_NODE_VERSIONS) {
|
|
if (version === 'current') {
|
|
nodeVersions.add(currentNodeVersion);
|
|
} else {
|
|
nodeVersions.add(version);
|
|
}
|
|
}
|
|
if (nodeVersions.size > 0) {
|
|
console.log('Building Docker images for Node.js versions:');
|
|
for (const v of nodeVersions) {
|
|
const isCurrent = v === currentNodeVersion ? ' (current)' : '';
|
|
console.log(` - ${v}${isCurrent}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine which Deno versions to build
|
|
if (envDenoVersion) {
|
|
denoVersions.add(envDenoVersion);
|
|
console.log(`Building Docker image for CI matrix Deno version: ${envDenoVersion}`);
|
|
} else if (!envNodeVersion && !envCliVersion) {
|
|
// Build all Deno versions only if no specific runtime was requested
|
|
for (const version of WELL_KNOWN_DENO_VERSIONS) {
|
|
if (version === 'current') {
|
|
denoVersions.add(currentDenoVersion);
|
|
} else {
|
|
denoVersions.add(version);
|
|
}
|
|
}
|
|
if (denoVersions.size > 0) {
|
|
console.log('Building Docker images for Deno versions:');
|
|
for (const v of denoVersions) {
|
|
const isCurrent = v === currentDenoVersion ? ' (current)' : '';
|
|
console.log(` - ${v}${isCurrent}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine which CLI versions to build
|
|
if (envCliVersion) {
|
|
cliVersions.add(envCliVersion);
|
|
console.log(`Building Docker image for CI matrix CLI version: ${envCliVersion}`);
|
|
} else if (!envNodeVersion && !envDenoVersion) {
|
|
cliVersions.add(currentCliVersion);
|
|
console.log(`Building Docker image for CLI version: ${currentCliVersion}`);
|
|
}
|
|
|
|
let failed = 0;
|
|
let total = 0;
|
|
|
|
// Build Node.js images
|
|
for (const version of nodeVersions) {
|
|
total++;
|
|
const success = await buildNodeImage(version, repoRoot);
|
|
if (!success) {
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
// Build Deno images
|
|
for (const version of denoVersions) {
|
|
total++;
|
|
const success = await buildDenoImage(version, dockerToolchain, repoRoot);
|
|
if (!success) {
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
// Build CLI images
|
|
for (const version of cliVersions) {
|
|
total++;
|
|
const success = await buildCliImage(version, dockerToolchain, repoRoot);
|
|
if (!success) {
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
console.log('\n---');
|
|
console.log(`Built ${total - failed}/${total} images.`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Error:', err);
|
|
process.exit(1);
|
|
});
|