@toolbox-sdk/core
Version:
JavaScript Base SDK for interacting with the Toolbox service
162 lines (144 loc) • 5.44 kB
text/typescript
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as path from 'path';
import * as fs from 'fs-extra';
import {spawn} from 'child_process';
import {
getEnvVar,
accessSecretVersion,
createTmpFile,
downloadBlob,
getToolboxBinaryGcsPath,
delay,
} from './utils';
import {CustomGlobal} from './types';
const TOOLBOX_BINARY_NAME = 'toolbox';
const SERVER_READY_TIMEOUT_MS = 30000; // 30 seconds
const SERVER_READY_POLL_INTERVAL_MS = 2000; // 2 seconds
export default async function globalSetup(): Promise<void> {
console.log('\nJest Global Setup: Starting...');
try {
const projectId = getEnvVar('GOOGLE_CLOUD_PROJECT');
const toolboxVersion = getEnvVar('TOOLBOX_VERSION');
(globalThis as CustomGlobal).__GOOGLE_CLOUD_PROJECT__ = projectId;
// Fetch tools manifest and create temp file
const toolsManifest = await accessSecretVersion(
projectId,
'sdk_testing_tools',
);
const toolsFilePath = await createTmpFile(toolsManifest);
(globalThis as CustomGlobal).__TOOLS_FILE_PATH__ = toolsFilePath;
console.log(`Tools manifest stored at: ${toolsFilePath}`);
// Download toolbox binary
const toolboxGcsPath = getToolboxBinaryGcsPath(toolboxVersion);
const localToolboxPath = path.resolve(__dirname, TOOLBOX_BINARY_NAME);
console.log(
`Downloading toolbox binary from gs://genai-toolbox/${toolboxGcsPath} to ${localToolboxPath}...`,
);
await downloadBlob('genai-toolbox', toolboxGcsPath, localToolboxPath);
console.log('Toolbox binary downloaded successfully.');
// Make toolbox executable
await fs.chmod(localToolboxPath, 0o700);
// Start toolbox server
console.log('Starting toolbox server process...');
const serverProcess = spawn(
localToolboxPath,
['--tools-file', toolsFilePath],
{
stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin, pipe stdout/stderr
},
);
(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS__ = serverProcess;
serverProcess.stdout?.on('data', (data: Buffer) => {
console.log(`[ToolboxServer STDOUT]: ${data.toString().trim()}`);
});
serverProcess.stderr?.on('data', (data: Buffer) => {
console.error(`[ToolboxServer STDERR]: ${data.toString().trim()}`);
});
serverProcess.on('error', err => {
console.error('Toolbox server process error:', err);
throw new Error('Failed to start toolbox server process.');
});
serverProcess.on('exit', (code, signal) => {
console.log(
`Toolbox server process exited with code ${code}, signal ${signal}.`,
);
if (
(globalThis as CustomGlobal).__TOOLBOX_SERVER_PROCESS__ &&
!(globalThis as CustomGlobal).__SERVER_TEARDOWN_INITIATED__
) {
console.error('Toolbox server exited prematurely during setup.');
}
});
// Wait for server to start (basic poll check)
let started = false;
const startTime = Date.now();
while (Date.now() - startTime < SERVER_READY_TIMEOUT_MS) {
if (
serverProcess.pid &&
!serverProcess.killed &&
serverProcess.exitCode === null
) {
console.log(
'Toolbox server process appears to be running. Polling for stability...',
);
await delay(SERVER_READY_POLL_INTERVAL_MS * 2);
if (serverProcess.exitCode === null) {
console.log(
'Toolbox server started successfully (process is active).',
);
started = true;
break;
} else {
console.log('Toolbox server process exited after initial start.');
break;
}
}
await delay(SERVER_READY_POLL_INTERVAL_MS);
console.log('Checking if toolbox server is started...');
}
if (!started) {
if (serverProcess && !serverProcess.killed) {
serverProcess.kill('SIGTERM');
}
throw new Error(
`Toolbox server failed to start within ${SERVER_READY_TIMEOUT_MS / 1000} seconds.`,
);
}
console.log('Jest Global Setup: Completed successfully.');
} catch (error) {
console.error('Jest Global Setup Failed:', error);
// Attempt to kill server if it started partially
const serverProcess = (globalThis as CustomGlobal)
.__TOOLBOX_SERVER_PROCESS__;
if (serverProcess && !serverProcess.killed) {
console.log('Attempting to terminate partially started server...');
serverProcess.kill('SIGKILL');
}
// Clean up temp file if created
const toolsFilePath = (globalThis as CustomGlobal).__TOOLS_FILE_PATH__;
if (toolsFilePath) {
try {
await fs.remove(toolsFilePath);
} catch (e) {
console.error(
'Error removing temp tools file during setup failure:',
e,
);
}
}
(globalThis as CustomGlobal).__GOOGLE_CLOUD_PROJECT__ = undefined;
throw error;
}
}