diff --git a/.changeset/tame-cobras-kick.md b/.changeset/tame-cobras-kick.md new file mode 100644 index 00000000..eec42fc2 --- /dev/null +++ b/.changeset/tame-cobras-kick.md @@ -0,0 +1,5 @@ +--- +"@cartesi/cli": patch +--- + +changes to run to work better with detached stdin (no shell) diff --git a/apps/cli/src/commands/run.ts b/apps/cli/src/commands/run.ts index 3d02f11f..f9047fc3 100755 --- a/apps/cli/src/commands/run.ts +++ b/apps/cli/src/commands/run.ts @@ -20,6 +20,7 @@ import { DEFAULT_SDK_VERSION, PREFERRED_PORT } from "../config.js"; import { AVAILABLE_SERVICES, deployApplication, + host, removeApplication, type RollupsDeployment, startEnvironment, @@ -38,32 +39,17 @@ const commaSeparatedList = (value: string) => value.split(","); const shell = async (options: { build?: CommandUnknownOpts; + deployment?: RollupsDeployment; epochLength: number; log?: CommandUnknownOpts; projectName: string; prt?: boolean; + salt: number; }) => { const { build, epochLength, log, projectName, prt } = options; - // keep track of last deployment - let lastDeployment: RollupsDeployment | undefined; - let salt = 0; - - // deploy for the first time - const hash = getMachineHash(); - if (hash) { - lastDeployment = await deploy({ - epochLength, - hash, - projectName, - prt, - salt: numberToHex(salt++, { size: 32 }), - }); - } else { - console.warn( - chalk.yellow("machine snapshot not found, waiting for build"), - ); - } + let lastDeployment = options.deployment; + let salt = options.salt; while (true) { try { @@ -313,11 +299,15 @@ export const createRunCommand = () => { // configure optional anvil fork const forkConfig = await configureFork(options); + // if TTY is not attached, run on foreground (not detached) + const detach = process.stdin.isTTY; + // run compose environment (detached) - const { address, config } = await startEnvironment({ + const { cmd, config } = await startEnvironment({ blockTime, cpus, defaultBlock, + detach, dryRun, forkConfig, memory, @@ -328,6 +318,9 @@ export const createRunCommand = () => { verbose, }); + // host address + const address = `${host}:${port}`; + if (dryRun && config) { // just show the docker compose configuration and quit process.stdout.write(config); @@ -346,6 +339,27 @@ export const createRunCommand = () => { services, }); + // deploy the application + let deployment: RollupsDeployment | undefined; + let salt = 0; + const prt = !authority; + const hash = getMachineHash(); + if (hash) { + deployment = await deploy({ + epochLength, + hash, + projectName, + prt, + salt: numberToHex(salt++, { size: 32 }), + }); + } else { + console.warn( + chalk.yellow( + "machine snapshot not found, waiting for build", + ), + ); + } + const shutdown = async () => { progress.start(`${chalk.cyan(projectName)} stopping...`); try { @@ -359,23 +373,41 @@ export const createRunCommand = () => { process.exit(0); }; - // inhibit SIGINT and SIGTERM, will be handled gracefully by the shell - process.on("SIGINT", () => {}); - process.on("SIGTERM", () => {}); + if (detach) { + // inhibit SIGINT and SIGTERM, will be handled gracefully by the shell + process.on("SIGINT", () => {}); + process.on("SIGTERM", () => {}); - const log = program.parent?.commands.find( - (c) => c.name() === "logs", - ); - const build = program.parent?.commands.find( - (c) => c.name() === "build", - ); - await shell({ - build, - epochLength, - log, - projectName, - prt: !authority, - }); - await shutdown(); + const log = program.parent?.commands.find( + (c) => c.name() === "logs", + ); + const build = program.parent?.commands.find( + (c) => c.name() === "build", + ); + await shell({ + build, + deployment, + epochLength, + log, + projectName, + prt, + salt, + }); + await shutdown(); + } else { + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + try { + await cmd; + } catch (error: unknown) { + if (error instanceof ExecaError) { + // just continue gracefully + if (error.exitCode === 130) { + return; + } + throw error; + } + } + } }); }; diff --git a/apps/cli/src/exec/rollups.ts b/apps/cli/src/exec/rollups.ts index d5e25c9b..383137cb 100644 --- a/apps/cli/src/exec/rollups.ts +++ b/apps/cli/src/exec/rollups.ts @@ -135,7 +135,7 @@ type Service = { errorTitle?: string; // title of the service when it is not healthy }; -const host = "http://127.0.0.1"; +export const host = "http://127.0.0.1"; // services configuration const baseServices: Service[] = [ @@ -242,6 +242,7 @@ export const startEnvironment = async (options: { blockTime: number; cpus?: number; defaultBlock: "latest" | "safe" | "pending" | "finalized"; + detach: boolean; dryRun: boolean; forkConfig?: ForkConfig; memory?: number; @@ -255,6 +256,7 @@ export const startEnvironment = async (options: { blockTime, cpus, defaultBlock, + detach, dryRun, forkConfig, memory, @@ -265,8 +267,6 @@ export const startEnvironment = async (options: { verbose, } = options; - const address = `${host}:${port}`; - // setup the environment variable used in docker compose const env: NodeJS.ProcessEnv = { CARTESI_BLOCKCHAIN_DEFAULT_BLOCK: defaultBlock, @@ -274,16 +274,19 @@ export const startEnvironment = async (options: { CARTESI_LOG_LEVEL: verbose ? "debug" : "info", }; + // local dev environment, we don't need security + const databasePassword = "password"; + const files = [ anvil({ blockTime, forkConfig, imageTag: runtimeVersion, }), - database({ imageTag: runtimeVersion, password: "password" }), + database({ imageTag: runtimeVersion, password: databasePassword }), node({ cpus, - databasePassword: "password", + databasePassword, defaultBlock, forkChainId: forkConfig?.chainId, imageTag: runtimeVersion, @@ -298,7 +301,7 @@ export const startEnvironment = async (options: { explorer({ imageTag: "1.4.0", apiTag: "1.1.0", - databasePassword: "password", + databasePassword, port, }), ); @@ -316,7 +319,7 @@ export const startEnvironment = async (options: { const composeArgs = ["compose", "-f", "-", "--project-directory", "."]; // run in detached mode (background) - const upArgs = ["--detach"]; + const upArgs = detach ? ["--detach"] : []; // merge files, following Docker Compose merge rules const composeFile = concat([{ name: projectName }, ...files]); @@ -330,25 +333,21 @@ export const startEnvironment = async (options: { { env, input: stringify(composeFile, { lineWidth: 0, indent: 2 }) }, ); - return { address, config }; + return { config }; } - // pull images first - // const pullArgs = ["--policy", "missing"]; - // await execa("docker", [...composeArgs, "pull", ...pullArgs], { - // env, - ////FIXME: stdio and input won't work together - // stdio: "inherit", - // input: composeFile.build() - // }); - // run compose - await execa("docker", [...composeArgs, "up", ...upArgs], { + const cmd = execa("docker", [...composeArgs, "up", ...upArgs], { env, input: stringify(composeFile, { lineWidth: 0, indent: 2 }), }); - return { address }; + // if detached, wait to finish + if (detach) { + await cmd; + } + + return { cmd }; }; /**