mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(tools-dev): print source-install od CLI hints after start
After tools-dev starts the daemon on an ephemeral port, print how to run `pnpm exec od` and export OD_DAEMON_URL so source installs do not hit the system BSD `od` binary or the packaged Docker default port. Related to #2801
This commit is contained in:
parent
f27fbfb3f1
commit
b80de1414b
3 changed files with 70 additions and 5 deletions
16
tools/dev/src/cli-hints.ts
Normal file
16
tools/dev/src/cli-hints.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
export function formatSourceInstallCliHints(args: {
|
||||||
|
daemonUrl: string | null;
|
||||||
|
odBinPath: string;
|
||||||
|
}): string[] {
|
||||||
|
if (args.daemonUrl == null) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
"",
|
||||||
|
" Source install CLI",
|
||||||
|
"",
|
||||||
|
" ➜ Run: pnpm exec od skills list",
|
||||||
|
` ➜ URL: export OD_DAEMON_URL=${args.daemonUrl}`,
|
||||||
|
` ➜ Note: system 'od' is not Open Design — use pnpm exec od or ${args.odBinPath}`,
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -64,6 +64,7 @@ import {
|
||||||
waitForWebRuntime,
|
waitForWebRuntime,
|
||||||
} from "./sidecar-client.js";
|
} from "./sidecar-client.js";
|
||||||
import { ensureDaemonGateForDesktop } from "./desktop-auth-gate.js";
|
import { ensureDaemonGateForDesktop } from "./desktop-auth-gate.js";
|
||||||
|
import { formatSourceInstallCliHints } from "./cli-hints.js";
|
||||||
import { resolveSharedPortsFromRunningState } from "./shared-ports.js";
|
import { resolveSharedPortsFromRunningState } from "./shared-ports.js";
|
||||||
|
|
||||||
type CliOptions = ToolDevOptions & {
|
type CliOptions = ToolDevOptions & {
|
||||||
|
|
@ -183,12 +184,28 @@ function printStartSection(result: Partial<Record<ToolDevAppName, unknown>>, hea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function printStartResult(result: Partial<Record<ToolDevAppName, unknown>>, options: CliOptions, heading = "tools-dev start"): void {
|
function printSourceInstallCliHints(config: ToolDevConfig, daemonUrl: string | null): void {
|
||||||
|
for (const line of formatSourceInstallCliHints({
|
||||||
|
daemonUrl,
|
||||||
|
odBinPath: path.join(config.workspaceRoot, "node_modules/.bin/od"),
|
||||||
|
})) {
|
||||||
|
process.stdout.write(`${line}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printStartResult(
|
||||||
|
result: Partial<Record<ToolDevAppName, unknown>>,
|
||||||
|
options: CliOptions,
|
||||||
|
config: ToolDevConfig,
|
||||||
|
heading = "tools-dev start",
|
||||||
|
): void {
|
||||||
if (options.json === true) {
|
if (options.json === true) {
|
||||||
printJson(result);
|
printJson(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
printStartSection(result, heading);
|
printStartSection(result, heading);
|
||||||
|
const daemonStatus = asRecord(asRecord(result.daemon)?.status);
|
||||||
|
printSourceInstallCliHints(config, stringField(daemonStatus ?? {}, "url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function printStopSection(result: Partial<Record<ToolDevAppName, unknown>>, heading: string): void {
|
function printStopSection(result: Partial<Record<ToolDevAppName, unknown>>, heading: string): void {
|
||||||
|
|
@ -254,7 +271,11 @@ function printStatusResult(result: unknown, options: CliOptions, appName: string
|
||||||
process.stdout.write(`- ${appName ?? ALL_APPS.join("/")}: ${formatStatusSummary(result)}\n`);
|
process.stdout.write(`- ${appName ?? ALL_APPS.join("/")}: ${formatStatusSummary(result)}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function printRunForegroundResult(started: Partial<Record<ToolDevAppName, unknown>>, options: CliOptions): void {
|
function printRunForegroundResult(
|
||||||
|
started: Partial<Record<ToolDevAppName, unknown>>,
|
||||||
|
options: CliOptions,
|
||||||
|
config: ToolDevConfig,
|
||||||
|
): void {
|
||||||
if (options.json === true) {
|
if (options.json === true) {
|
||||||
printJson({ mode: "foreground", started });
|
printJson({ mode: "foreground", started });
|
||||||
return;
|
return;
|
||||||
|
|
@ -269,7 +290,8 @@ function printRunForegroundResult(started: Partial<Record<ToolDevAppName, unknow
|
||||||
process.stdout.write("\n Open Design dev server ready\n\n");
|
process.stdout.write("\n Open Design dev server ready\n\n");
|
||||||
if (webUrl != null) process.stdout.write(` ➜ Web: ${colorizeLink(normalizeDisplayUrl(webUrl))}\n`);
|
if (webUrl != null) process.stdout.write(` ➜ Web: ${colorizeLink(normalizeDisplayUrl(webUrl))}\n`);
|
||||||
if (daemonUrl != null) process.stdout.write(` ➜ Daemon: ${colorizeLink(normalizeDisplayUrl(daemonUrl))}\n`);
|
if (daemonUrl != null) process.stdout.write(` ➜ Daemon: ${colorizeLink(normalizeDisplayUrl(daemonUrl))}\n`);
|
||||||
process.stdout.write("\n Press Ctrl+C to stop\n\n");
|
printSourceInstallCliHints(config, daemonUrl);
|
||||||
|
process.stdout.write(" Press Ctrl+C to stop\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1044,7 +1066,7 @@ async function runForeground(config: ToolDevConfig, appName: string | undefined,
|
||||||
webUrl: async () => (await inspectWebRuntime(runtimeLookup(config)))?.url,
|
webUrl: async () => (await inspectWebRuntime(runtimeLookup(config)))?.url,
|
||||||
});
|
});
|
||||||
const started = await runSequential(targets, (target) => startApp(config, target, foregroundOptions, { targets }));
|
const started = await runSequential(targets, (target) => startApp(config, target, foregroundOptions, { targets }));
|
||||||
printRunForegroundResult(started, options);
|
printRunForegroundResult(started, options, config);
|
||||||
|
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
const keepAlive = setInterval(() => undefined, 60_000);
|
const keepAlive = setInterval(() => undefined, 60_000);
|
||||||
|
|
@ -1094,7 +1116,7 @@ addPortOptions(addSharedOptions(cli.command("start [app]", "Start daemon, web, d
|
||||||
webUrl: async () => (await inspectWebRuntime(runtimeLookup(config)))?.url,
|
webUrl: async () => (await inspectWebRuntime(runtimeLookup(config)))?.url,
|
||||||
});
|
});
|
||||||
const result = await runSequential(targets, (target) => startApp(config, target, options, { targets }));
|
const result = await runSequential(targets, (target) => startApp(config, target, options, { targets }));
|
||||||
printStartResult(result, options);
|
printStartResult(result, options, config);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
27
tools/dev/tests/cli-hints.test.ts
Normal file
27
tools/dev/tests/cli-hints.test.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
|
import { formatSourceInstallCliHints } from "../src/cli-hints.js";
|
||||||
|
|
||||||
|
describe("formatSourceInstallCliHints", () => {
|
||||||
|
it("returns empty output when the daemon URL is unavailable", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
formatSourceInstallCliHints({
|
||||||
|
daemonUrl: null,
|
||||||
|
odBinPath: "/repo/node_modules/.bin/od",
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prints pnpm exec od guidance and OD_DAEMON_URL export (#2801)", () => {
|
||||||
|
const text = formatSourceInstallCliHints({
|
||||||
|
daemonUrl: "http://127.0.0.1:49980",
|
||||||
|
odBinPath: "/repo/node_modules/.bin/od",
|
||||||
|
}).join("\n");
|
||||||
|
|
||||||
|
assert.match(text, /pnpm exec od skills list/);
|
||||||
|
assert.match(text, /export OD_DAEMON_URL=http:\/\/127\.0\.0\.1:49980/);
|
||||||
|
assert.match(text, /\/repo\/node_modules\/\.bin\/od/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue