portable-agent-layer 0.85.7 → 0.85.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/doctor-tools.ts +133 -0
- package/src/cli/index.ts +3 -0
package/package.json
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The doctor's git and gh checks. Neither is required: without git, project
|
|
3
|
+
* matching by remote and the update checks skip; without gh, nothing does yet.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { findBinaryOnPath } from "../hooks/lib/which";
|
|
8
|
+
|
|
9
|
+
export interface DoctorLine {
|
|
10
|
+
level: "ok" | "warn" | "info";
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type LinuxFamily = "debian" | "fedora" | "arch" | "alpine" | "suse";
|
|
15
|
+
|
|
16
|
+
interface Host {
|
|
17
|
+
platform: NodeJS.Platform;
|
|
18
|
+
osRelease: string;
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const GH_LINUX_DOCS = "https://github.com/cli/cli/blob/trunk/docs/install_linux.md";
|
|
23
|
+
|
|
24
|
+
function osReleaseIds(osRelease: string): string[] {
|
|
25
|
+
const field = (key: string) =>
|
|
26
|
+
osRelease.match(new RegExp(String.raw`^${key}="?([^"\n]*)"?$`, "m"))?.[1] ?? "";
|
|
27
|
+
return `${field("ID")} ${field("ID_LIKE")}`.split(/\s+/).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function linuxFamily(osRelease: string): LinuxFamily | null {
|
|
31
|
+
const ids = osReleaseIds(osRelease);
|
|
32
|
+
const families: [LinuxFamily, string[]][] = [
|
|
33
|
+
["debian", ["debian", "ubuntu"]],
|
|
34
|
+
["fedora", ["fedora", "rhel", "centos"]],
|
|
35
|
+
["arch", ["arch"]],
|
|
36
|
+
["alpine", ["alpine"]],
|
|
37
|
+
["suse", ["suse", "opensuse"]],
|
|
38
|
+
];
|
|
39
|
+
return families.find(([, names]) => names.some((n) => ids.includes(n)))?.[0] ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const GIT_ON_LINUX: Record<LinuxFamily, string> = {
|
|
43
|
+
debian: "sudo apt install git",
|
|
44
|
+
fedora: "sudo dnf install git",
|
|
45
|
+
arch: "sudo pacman -S git",
|
|
46
|
+
alpine: "sudo apk add git",
|
|
47
|
+
suse: "sudo zypper install git",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const GH_ON_LINUX: Partial<Record<LinuxFamily, string>> = {
|
|
51
|
+
fedora: "sudo dnf install gh",
|
|
52
|
+
arch: "sudo pacman -S github-cli",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function gitInstallHint(platform: NodeJS.Platform, osRelease: string): string {
|
|
56
|
+
if (platform === "win32") return "`winget install Git.Git`";
|
|
57
|
+
if (platform === "darwin") return "`brew install git` or `xcode-select --install`";
|
|
58
|
+
const family = linuxFamily(osRelease);
|
|
59
|
+
return family ? `\`${GIT_ON_LINUX[family]}\`` : "install git with your package manager";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function ghInstallHint(platform: NodeJS.Platform, osRelease: string): string {
|
|
63
|
+
if (platform === "win32") return "`winget install GitHub.cli`";
|
|
64
|
+
if (platform === "darwin") return "`brew install gh`";
|
|
65
|
+
const family = linuxFamily(osRelease);
|
|
66
|
+
const command = family && GH_ON_LINUX[family];
|
|
67
|
+
return command ? `\`${command}\`` : `see ${GH_LINUX_DOCS}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function run(binary: string, args: string[], timeoutMs: number) {
|
|
71
|
+
const result = Bun.spawnSync([binary, ...args], {
|
|
72
|
+
stdout: "pipe",
|
|
73
|
+
stderr: "ignore",
|
|
74
|
+
timeout: timeoutMs,
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
answered: !result.exitedDueToTimeout,
|
|
79
|
+
ok: result.success,
|
|
80
|
+
firstLine: result.stdout.toString().trim().split("\n")[0] ?? "",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function installed(name: string, timeoutMs: number) {
|
|
85
|
+
const binary = findBinaryOnPath(name);
|
|
86
|
+
if (!binary) return null;
|
|
87
|
+
const version = run(binary, ["--version"], timeoutMs);
|
|
88
|
+
return version.ok ? { binary, version: version.firstLine } : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function gitLine(host: Host): DoctorLine {
|
|
92
|
+
const git = installed("git", host.timeoutMs);
|
|
93
|
+
if (git) return { level: "ok", text: git.version };
|
|
94
|
+
return {
|
|
95
|
+
level: "warn",
|
|
96
|
+
text: `git — not found; project matching by remote, the update check and auto-update skip without it. ${gitInstallHint(host.platform, host.osRelease)}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function ghLine(host: Host): DoctorLine {
|
|
101
|
+
const gh = installed("gh", host.timeoutMs);
|
|
102
|
+
if (!gh)
|
|
103
|
+
return {
|
|
104
|
+
level: "info",
|
|
105
|
+
text: `gh — not installed (optional; lets PAL see PR, CI and release state — ${ghInstallHint(host.platform, host.osRelease)})`,
|
|
106
|
+
};
|
|
107
|
+
const auth = run(gh.binary, ["auth", "status"], host.timeoutMs);
|
|
108
|
+
if (!auth.answered)
|
|
109
|
+
return { level: "info", text: `${gh.version} — could not check the login in time` };
|
|
110
|
+
if (!auth.ok)
|
|
111
|
+
return {
|
|
112
|
+
level: "warn",
|
|
113
|
+
text: `${gh.version} — not logged in; run \`gh auth login\``,
|
|
114
|
+
};
|
|
115
|
+
return { level: "ok", text: `${gh.version}, logged in` };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readOsRelease(): string {
|
|
119
|
+
try {
|
|
120
|
+
return readFileSync("/etc/os-release", "utf-8");
|
|
121
|
+
} catch {
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function versionControlLines(host: Partial<Host> = {}): DoctorLine[] {
|
|
127
|
+
const resolved: Host = {
|
|
128
|
+
platform: host.platform ?? process.platform,
|
|
129
|
+
osRelease: host.osRelease ?? (process.platform === "linux" ? readOsRelease() : ""),
|
|
130
|
+
timeoutMs: host.timeoutMs ?? 5000,
|
|
131
|
+
};
|
|
132
|
+
return [gitLine(resolved), ghLine(resolved)];
|
|
133
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { telosStatus } from "../hooks/lib/telos-topics";
|
|
|
56
56
|
import { findBinaryOnPath } from "../hooks/lib/which";
|
|
57
57
|
import { log } from "../targets/lib";
|
|
58
58
|
import { builtinToolVerbs, runBuiltinTool } from "./builtin-tools";
|
|
59
|
+
import { versionControlLines } from "./doctor-tools";
|
|
59
60
|
import { checkPendingMigrations } from "./migrate";
|
|
60
61
|
import { findSessionAgent, NO_SESSION_AGENT_MESSAGE } from "./session-agent";
|
|
61
62
|
|
|
@@ -842,6 +843,8 @@ function doctor(silent = false): DoctorResult {
|
|
|
842
843
|
: info(
|
|
843
844
|
`rtk — not installed (optional; enables Bash output compression — ${rtkInstallHint()})`
|
|
844
845
|
);
|
|
846
|
+
const print = { ok, warn, info };
|
|
847
|
+
for (const line of versionControlLines()) print[line.level](line.text);
|
|
845
848
|
|
|
846
849
|
console.log("");
|
|
847
850
|
log.info("PAL state");
|