Skip to content
34 changes: 19 additions & 15 deletions packages/docker-doctor/src/agents/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawn } from "node:child_process";
import { once } from "node:events";

interface ClipboardCommand {
command: string;
Expand All @@ -21,22 +22,25 @@ const getClipboardCommands = (): ClipboardCommand[] => {
];
};

const tryCopy = async ({ command, args }: ClipboardCommand, text: string) =>
/* eslint-disable promise/avoid-new */
await new Promise<boolean>((resolve) => {
const child = spawn(command, args, {
stdio: ["pipe", "ignore", "ignore"],
});

child.once("error", () => {
resolve(false);
});
child.once("exit", (code) => {
resolve(code === 0);
});
child.stdin.end(text);
const tryCopy = async (
{ command, args }: ClipboardCommand,
text: string
): Promise<boolean> => {
const child = spawn(command, args, {
stdio: ["pipe", "ignore", "ignore"],
});
/* eslint-enable promise/avoid-new */

const exited = once(child, "exit");
child.stdin.end(text);

try {
await exited;
} catch {
return false;
}

return child.exitCode === 0;
};

const tryCommands = async (
commands: ClipboardCommand[],
Expand Down
3 changes: 1 addition & 2 deletions packages/docker-doctor/src/agents/diagnostics-dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export const TRUST_BOUNDARY_NOTE =

const UNSAFE_FILE_CHARS = /[^a-z0-9-]+/giu;

// ":12" for a diagnostic on line 12, nothing for one without a line.
const lineSuffix = (line: number | undefined): string =>
export const lineSuffix = (line: number | undefined): string =>
line === undefined ? "" : `:${line}`;

const ruleFileName = (rule: string): string => {
Expand Down
5 changes: 1 addition & 4 deletions packages/docker-doctor/src/agents/handoff-payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import { findRule } from "@docker-doctor/core";
import {
DIAGNOSTICS_DIR_NAME,
groupDiagnosticsByRule,
lineSuffix,
TRUST_BOUNDARY_NOTE,
} from "./diagnostics-dir";
import { sanitizeMessage, sanitizePath } from "./sanitize";

// ":12" for a diagnostic on line 12, nothing for one without a line.
const lineSuffix = (line: number | undefined): string =>
line === undefined ? "" : `:${line}`;

const MAX_FILES_PER_RULE = 5;

const SEVERITY_RANK: Record<Diagnostic["severity"], number> = {
Expand Down
16 changes: 11 additions & 5 deletions packages/docker-doctor/src/agents/is-command-available.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import path from "node:path";

const WINDOWS_EXTENSIONS = [".exe", ".cmd", ".bat"];

const isExecutableFile = (filePath: string): boolean => {
try {
fs.accessSync(filePath, fs.constants.X_OK);

return true;
} catch {
return false;
}
};

export const isCommandAvailable = (command: string): boolean => {
const pathValue = process.env.PATH ?? "";
const extensions = process.platform === "win32" ? WINDOWS_EXTENSIONS : [""];
Expand All @@ -13,12 +23,8 @@ export const isCommandAvailable = (command: string): boolean => {
}

for (const extension of extensions) {
try {
fs.accessSync(path.join(dir, command + extension), fs.constants.X_OK);

if (isExecutableFile(path.join(dir, command + extension))) {
return true;
} catch {
// Not in this directory — keep looking.
}
}
}
Expand Down
37 changes: 18 additions & 19 deletions packages/docker-doctor/src/agents/launch-agent.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import { spawn } from "node:child_process";
import { once } from "node:events";

import type { LaunchableAgentId } from "./launchable-agents";
import { AGENT_AUTO_FLAGS, AGENT_BINARIES } from "./launchable-agents";

// Launches the agent's CLI with the prompt as its initial argument, handing it
// this terminal. The agent starts in `cwd`, the scanned directory. Every path
// in the prompt is relative to it, and the shell's own directory can be a
// different project. Resolves true when the agent process exits normally,
// false when it could not be started (caller falls back to the clipboard
// path).
// different project. Resolves true once the agent exits, whatever its exit
// code, and false when it could not be started (the caller falls back to the
// clipboard path).
export const launchAgent = async (
agentId: LaunchableAgentId,
prompt: string,
cwd: string
): Promise<boolean> =>
/* eslint-disable promise/avoid-new */
await new Promise((resolve) => {
const child = spawn(
AGENT_BINARIES[agentId],
[...AGENT_AUTO_FLAGS[agentId], prompt],
{ cwd, stdio: "inherit" }
);
): Promise<boolean> => {
const child = spawn(
AGENT_BINARIES[agentId],
[...AGENT_AUTO_FLAGS[agentId], prompt],
{ cwd, stdio: "inherit" }
);

child.once("error", () => {
resolve(false);
});
child.once("exit", () => {
resolve(true);
});
});
/* eslint-enable promise/avoid-new */
try {
await once(child, "exit");
} catch {
return false;
}

return true;
};
Loading
Loading