diff --git a/package-lock.json b/package-lock.json index 1079eff..31a0478 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15568,6 +15568,9 @@ "vite-tsconfig-paths": "^5.1.4", "zod": "^4.2.1" }, + "bin": { + "dashboard": "bin.mjs" + }, "devDependencies": { "@tanstack/devtools-vite": "^0.3.11", "@testing-library/dom": "^10.4.0", diff --git a/packages/cli/cli.ts b/packages/cli/cli.ts index 8cdae7e..479e23c 100644 --- a/packages/cli/cli.ts +++ b/packages/cli/cli.ts @@ -1,6 +1,12 @@ #!/usr/bin/env node /* v8 ignore file -- @preserve */ -import { doctor, getVersion, init, workerStart } from "./commands.js"; +import { + dashboard, + doctor, + getVersion, + init, + workerStart, +} from "./commands.js"; import { withErrorHandling } from "./errors.js"; import { Command } from "commander"; @@ -38,4 +44,10 @@ workerCmd ) .action(withErrorHandling(workerStart)); +// dashboard +program + .command("dashboard") + .description("start the dashboard to view workflow runs") + .action(withErrorHandling(dashboard)); + await program.parseAsync(process.argv); diff --git a/packages/cli/commands.ts b/packages/cli/commands.ts index c2e13f8..964f009 100644 --- a/packages/cli/commands.ts +++ b/packages/cli/commands.ts @@ -10,6 +10,7 @@ import * as p from "@clack/prompts"; import { consola } from "consola"; import { config as loadDotenv } from "dotenv"; import { createJiti } from "jiti"; +import { spawn } from "node:child_process"; import { existsSync, mkdirSync, @@ -308,6 +309,70 @@ export async function workerStart(cliOptions: WorkerConfig): Promise { } } +/** + * openworkflow dashboard + * Starts the dashboard by delegating to `@openworkflow/dashboard` via npx. + */ +export async function dashboard(): Promise { + consola.start("Starting dashboard..."); + + const { configFile } = await loadConfigWithEnv(); + if (!configFile) { + throw new CLIError( + "No config file found.", + "Run `ow init` to create a config file before starting the dashboard.", + ); + } + consola.info(`Using config: ${configFile}`); + + // eslint-disable-next-line sonarjs/no-os-command-from-path + const child = spawn("npx", ["@openworkflow/dashboard"], { + stdio: "inherit", + }); + + await new Promise((resolve, reject) => { + /** remove signal handlers after the child exits */ + function cleanupSignalHandlers(): void { + process.off("SIGINT", signalHandler); + process.off("SIGTERM", signalHandler); + } + + child.on("error", (error) => { + cleanupSignalHandlers(); + reject( + new CLIError( + "Failed to start dashboard.", + `Could not spawn npx: ${error.message}`, + ), + ); + }); + + child.on("exit", (code) => { + cleanupSignalHandlers(); + if (code === 0 || code === null) { + resolve(); + } else { + reject( + new CLIError( + "Dashboard exited with an error.", + `Exit code: ${String(code)}`, + ), + ); + } + }); + + /** + * Graceful shutdown on signals. + * @param signal - Signal + */ + function signalHandler(signal: NodeJS.Signals): void { + child.kill(signal); + } + process.on("SIGINT", signalHandler); + process.on("SIGTERM", signalHandler); + }); +} + // ----------------------------------------------------------------------------- /** diff --git a/packages/dashboard/bin.mjs b/packages/dashboard/bin.mjs new file mode 100755 index 0000000..c8da1dc --- /dev/null +++ b/packages/dashboard/bin.mjs @@ -0,0 +1,10 @@ +#!/usr/bin/env node +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +// simple wrapper to load the dashboard server since the index.mjs file does not +// have a shebang line +const __dirname = dirname(fileURLToPath(import.meta.url)); +const serverPath = join(__dirname, ".output", "server", "index.mjs"); + +await import(serverPath); diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 5117e04..744f9b8 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -2,10 +2,16 @@ "name": "@openworkflow/dashboard", "private": true, "type": "module", + "bin": "./bin.mjs", + "files": [ + ".output", + "bin.mjs" + ], "scripts": { "build": "vite build", "dev": "vite dev --port 3000", "preview": "vite preview", + "start": "node ./.output/server/index.mjs", "test": "vitest run", "typecheck": "tsc --noEmit" }, diff --git a/turbo.json b/turbo.json index 78434fa..87f27ef 100644 --- a/turbo.json +++ b/turbo.json @@ -3,7 +3,7 @@ "tasks": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**"] + "outputs": ["dist/**", ".output/**"] } } }