/**
* Copyright 2024 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 { RuntimeManager } from '@genkit-ai/tools-common/manager';
import { startServer } from '@genkit-ai/tools-common/server';
import { logger } from '@genkit-ai/tools-common/utils';
import { spawn } from 'child_process';
import { Command } from 'commander';
import getPort, { makeRange } from 'get-port';
import open from 'open';
import { startManager } from '../utils/manager-utils';
interface RunOptions {
noui?: boolean;
port?: string;
open?: boolean;
}
/** Command to run code in dev mode and/or the Dev UI. */
export const start = new Command('start')
.description('runs a command in Genkit dev mode')
.option('-n, --noui', 'do not start the Dev UI', false)
.option('-p, --port <port>', 'port for the Dev UI')
.option('-o, --open', 'Open the browser on UI start up')
.action(async (options: RunOptions) => {
// Always start the manager.
let managerPromise: Promise<RuntimeManager> = startManager(true);
if (!options.noui) {
let port: number;
if (options.port) {
port = Number(options.port);
if (isNaN(port) || port < 0) {
logger.error(`"${options.port}" is not a valid port number`);
return;
}
} else {
port = await getPort({ port: makeRange(4000, 4099) });
}
managerPromise = managerPromise.then((manager) => {
startServer(manager, port);
return manager;
});
if (options.open) {
open(`http://localhost:${port}`);
}
}
await managerPromise.then((manager: RuntimeManager) => {
const telemetryServerUrl = manager?.telemetryServerUrl;
return startRuntime(telemetryServerUrl);
});
});
async function startRuntime(telemetryServerUrl?: string) {
if (start.args.length > 0) {
return new Promise((urlResolver, reject) => {
const appProcess = spawn(start.args[0], start.args.slice(1), {
env: {
...process.env,
GENKIT_TELEMETRY_SERVER: telemetryServerUrl,
GENKIT_ENV: 'dev',
},
shell: process.platform === 'win32',
});
const originalStdIn = process.stdin;
appProcess.stderr?.pipe(process.stderr);
appProcess.stdout?.pipe(process.stdout);
process.stdin?.pipe(appProcess.stdin);
appProcess.on('error', (error): void => {
console.log(`Error in app process: ${error}`);
reject(error);
process.exitCode = 1;
});
appProcess.on('exit', (code) => {
process.stdin?.pipe(originalStdIn);
if (code === 0) {
urlResolver(undefined);
} else {
reject(new Error(`app process exited with code ${code}`));
}
});
});
}
return new Promise(() => {}); // no runtime, return a hanging promise.
}