Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/custom-dashboard-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@openworkflow/cli": patch
---

Add a `--port` option to `openworkflow dashboard` so users can run the dashboard server on a custom port.
5 changes: 5 additions & 0 deletions packages/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ workerCmd
program
.command("dashboard")
.description("start the dashboard to view workflow runs")
.option(
"-p, --port <number>",
"custom port for the dashboard server",
Number.parseInt,
)
.option("--config <path>", "path to OpenWorkflow config file")
.action(withErrorHandling(dashboard));

Expand Down
48 changes: 48 additions & 0 deletions packages/cli/commands.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
discoverWorkflowFiles,
getDashboardSpawnOptions,
getClientFileName,
getConfigFileName,
getExampleWorkflowFileName,
getRunFileName,
validateDashboardPort,
} from "./commands.js";
import fs from "node:fs";
import os from "node:os";
Expand Down Expand Up @@ -141,3 +143,49 @@ describe("discoverWorkflowFiles", () => {
}
});
});

describe("getDashboardSpawnOptions", () => {
test("uses default npx command without a custom port env", () => {
const options = getDashboardSpawnOptions();

expect(options.command).toBe("npx");
expect(options.args).toEqual(["@openworkflow/dashboard"]);
expect(options.spawnOptions.env?.["PORT"]).toBeUndefined();
expect(options.spawnOptions.stdio).toBe("inherit");
});

test("sets PORT env when a custom dashboard port is provided", () => {
const options = getDashboardSpawnOptions(4321);

expect(options.command).toBe("npx");
expect(options.args).toEqual(["@openworkflow/dashboard"]);
expect(options.spawnOptions.env?.["PORT"]).toBe("4321");
expect(options.spawnOptions.stdio).toBe("inherit");
});
});

describe("validateDashboardPort", () => {
test("returns undefined when no custom port is provided", () => {
expect(validateDashboardPort()).toBeUndefined();
});

test("returns the port when it is within range", () => {
expect(validateDashboardPort(3001)).toBe(3001);
});

test("throws for non-integer ports", () => {
expect(() => validateDashboardPort(Number.NaN)).toThrow(
"Invalid dashboard port.",
);
expect(() => validateDashboardPort(3000.5)).toThrow(
"Invalid dashboard port.",
);
});

test("throws for out-of-range ports", () => {
expect(() => validateDashboardPort(0)).toThrow("Invalid dashboard port.");
expect(() => validateDashboardPort(65_536)).toThrow(
"Invalid dashboard port.",
);
});
});
67 changes: 61 additions & 6 deletions packages/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ interface CommandOptions {
config?: string;
}

interface DashboardOptions extends CommandOptions {
port?: number;
}

/**
* openworkflow -V | --version
* @returns the version string, or "-" if it cannot be determined
Expand Down Expand Up @@ -340,10 +344,59 @@ export async function workerStart(
/**
* openworkflow dashboard
* Starts the dashboard by delegating to `@openworkflow/dashboard` via npx.
* @param options - Command options
* @param port - Optional dashboard port.
* @returns Spawn configuration for launching the dashboard process.
*/
export function getDashboardSpawnOptions(port?: number): {
command: string;
args: string[];
spawnOptions: {
stdio: "inherit";
env?: NodeJS.ProcessEnv;
};
} {
return {
command: "npx",
args: ["@openworkflow/dashboard"],
spawnOptions: {
stdio: "inherit",
env:
port === undefined
? process.env
: { ...process.env, PORT: String(port) },
},
};
}

/**
* Validate dashboard port option.
* @param port - Optional dashboard port.
* @returns Validated dashboard port.
* @throws {CLIError} If the provided port is not an integer in the 1-65535 range.
*/
export async function dashboard(options: CommandOptions = {}): Promise<void> {
export function validateDashboardPort(port?: number): number | undefined {
if (port === undefined) {
return undefined;
}

if (!Number.isInteger(port) || port < 1 || port > 65_535) {
throw new CLIError(
"Invalid dashboard port.",
"Use an integer between 1 and 65535, for example `--port 3001`.",
);
}

return port;
}

/**
* Start the dashboard process.
* @param options - Dashboard command options.
* @returns Resolves when the dashboard process exits.
*/
export async function dashboard(options: DashboardOptions = {}): Promise<void> {
const configPath = options.config;
const port = validateDashboardPort(options.port);
consola.start("Starting dashboard...");

const { configFile } = await loadConfigWithEnv(configPath);
Expand All @@ -355,10 +408,12 @@ export async function dashboard(options: CommandOptions = {}): Promise<void> {
}
consola.info(`Using config: ${configFile}`);

// eslint-disable-next-line sonarjs/no-os-command-from-path
const child = spawn("npx", ["@openworkflow/dashboard"], {
stdio: "inherit",
});
const spawnConfig = getDashboardSpawnOptions(port);
const child = spawn(
spawnConfig.command,
spawnConfig.args,
spawnConfig.spawnOptions,
);

await new Promise<void>((resolve, reject) => {
/** remove signal handlers after the child exits */
Expand Down
22 changes: 20 additions & 2 deletions packages/docs/docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,26 @@ bunx @openworkflow/cli dashboard

</CodeGroup>

This launches the OpenWorkflow dashboard on `http://localhost:3000`. See
[Dashboard](/docs/dashboard) for details.
The dashboard starts on `http://localhost:3000` by default.

To use a custom port:

<CodeGroup>
```bash npm
npx @openworkflow/cli dashboard --port 4000
```

```bash pnpm
pnpx @openworkflow/cli dashboard --port 4000
```

```bash bun
bunx @openworkflow/cli dashboard --port 4000
```

</CodeGroup>

See [Dashboard](/docs/dashboard) for details.

### `doctor`

Expand Down
19 changes: 18 additions & 1 deletion packages/docs/docs/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,24 @@ bunx @openworkflow/cli dashboard

</CodeGroup>

The dashboard starts on `http://localhost:3000`.
The dashboard starts on `http://localhost:3000` by default.

To use a custom port:

<CodeGroup>
```bash npm
npx @openworkflow/cli dashboard --port 4000
```

```bash pnpm
pnpx @openworkflow/cli dashboard --port 4000
```

```bash bun
bunx @openworkflow/cli dashboard --port 4000
```

</CodeGroup>

## Requirements

Expand Down
Loading