





















1-import { accessSync, constants, existsSync, readFileSync, realpathSync } from "node:fs";
1+import { existsSync, readFileSync } from "node:fs";
22import { homedir } from "node:os";
3-import { basename, dirname, join, resolve, sep, win32 } from "node:path";
3+import { dirname, join, resolve } from "node:path";
44import { fileURLToPath } from "node:url";
5-import { spawnProcessSync } from "./utils/child-process.ts";
6576// =============================================================================
87// Package Detection
@@ -20,321 +19,6 @@ export const isBunBinary =
2019import.meta.url.includes("~BUN") ||
2120import.meta.url.includes("%7EBUN");
222123-/** Detect if Bun is the runtime (compiled binary or bun run) */
24-export const isBunRuntime = !!process.versions.bun;
25-26-// =============================================================================
27-// Install Method Detection
28-// =============================================================================
29-30-export type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "unknown";
31-32-interface SelfUpdateCommandStep {
33-command: string;
34-args: string[];
35-display: string;
36-}
37-38-export interface SelfUpdateCommand extends SelfUpdateCommandStep {
39-steps?: SelfUpdateCommandStep[];
40-}
41-42-function makeSelfUpdateCommand(
43-installStep: SelfUpdateCommandStep,
44-uninstallStep?: SelfUpdateCommandStep,
45-): SelfUpdateCommand {
46-if (!uninstallStep) {
47-return installStep;
48-}
49-return {
50- ...installStep,
51-display: `${uninstallStep.display} && ${installStep.display}`,
52-steps: [uninstallStep, installStep],
53-};
54-}
55-56-function makeSelfUpdateCommandStep(command: string, args: string[]): SelfUpdateCommandStep {
57-return {
58- command,
59- args,
60-display: [command, ...args].map((arg) => (/\s/.test(arg) ? `"${arg}"` : arg)).join(" "),
61-};
62-}
63-64-export function detectInstallMethod(): InstallMethod {
65-if (isBunBinary) {
66-return "bun-binary";
67-}
68-69-const resolvedPath = `${currentDir}\0${process.execPath || ""}`.toLowerCase().replace(/\\/g, "/");
70-71-if (resolvedPath.includes("/pnpm/") || resolvedPath.includes("/.pnpm/")) {
72-return "pnpm";
73-}
74-if (resolvedPath.includes("/yarn/") || resolvedPath.includes("/.yarn/")) {
75-return "yarn";
76-}
77-if (isBunRuntime || resolvedPath.includes("/install/global/node_modules/")) {
78-return "bun";
79-}
80-if (resolvedPath.includes("/npm/") || resolvedPath.includes("/node_modules/")) {
81-return "npm";
82-}
83-84-return "unknown";
85-}
86-87-function getInferredNpmInstall(): { root: string; prefix: string } | undefined {
88-const packageDir = getPackageDir();
89-const path =
90-process.platform === "win32" || packageDir.includes("\\") ? win32 : { basename, dirname };
91-const parent = path.dirname(packageDir);
92-let root: string | undefined;
93-if (
94-path.basename(parent).startsWith("@") &&
95-path.basename(path.dirname(parent)) === "node_modules"
96-) {
97-root = path.dirname(parent);
98-} else if (path.basename(parent) === "node_modules") {
99-root = parent;
100-}
101-if (!root) {
102-return undefined;
103-}
104-const rootParent = path.dirname(root);
105-if (path.basename(rootParent) === "lib") {
106-return { root, prefix: path.dirname(rootParent) };
107-}
108-// Windows global npm prefixes use `<prefix>\\node_modules`, which is
109-// indistinguishable from local project installs by path shape alone. Do not
110-// infer unsupported Windows custom prefixes without `npm root -g` evidence.
111-return undefined;
112-}
113-114-function getSelfUpdateCommandForMethod(
115-method: InstallMethod,
116-installedPackageName: string,
117-updatePackageName = installedPackageName,
118-npmCommand?: string[],
119-): SelfUpdateCommand | undefined {
120-switch (method) {
121-case "bun-binary":
122-return undefined;
123-case "pnpm":
124-return makeSelfUpdateCommand(
125-makeSelfUpdateCommandStep("pnpm", ["install", "-g", "--ignore-scripts", updatePackageName]),
126-updatePackageName === installedPackageName
127- ? undefined
128- : makeSelfUpdateCommandStep("pnpm", ["remove", "-g", installedPackageName]),
129-);
130-case "yarn":
131-return makeSelfUpdateCommand(
132-makeSelfUpdateCommandStep("yarn", ["global", "add", "--ignore-scripts", updatePackageName]),
133-updatePackageName === installedPackageName
134- ? undefined
135- : makeSelfUpdateCommandStep("yarn", ["global", "remove", installedPackageName]),
136-);
137-case "bun":
138-return makeSelfUpdateCommand(
139-makeSelfUpdateCommandStep("bun", ["install", "-g", "--ignore-scripts", updatePackageName]),
140-updatePackageName === installedPackageName
141- ? undefined
142- : makeSelfUpdateCommandStep("bun", ["uninstall", "-g", installedPackageName]),
143-);
144-case "npm": {
145-const [command = "npm", ...npmArgs] = npmCommand ?? [];
146-const inferred = npmCommand?.length ? undefined : getInferredNpmInstall();
147-const prefixArgs = [...npmArgs, ...(inferred ? ["--prefix", inferred.prefix] : [])];
148-const installStep = makeSelfUpdateCommandStep(command, [
149- ...prefixArgs,
150-"install",
151-"-g",
152-"--ignore-scripts",
153-updatePackageName,
154-]);
155-const uninstallStep =
156-updatePackageName === installedPackageName
157- ? undefined
158- : makeSelfUpdateCommandStep(command, [
159- ...prefixArgs,
160-"uninstall",
161-"-g",
162-installedPackageName,
163-]);
164-return makeSelfUpdateCommand(installStep, uninstallStep);
165-}
166-case "unknown":
167-return undefined;
168-}
169-return undefined;
170-}
171-172-function readCommandOutput(
173-command: string,
174-args: string[],
175-options: { requireSuccess?: boolean } = {},
176-): string | undefined {
177-const result = spawnProcessSync(command, args, {
178-encoding: "utf-8",
179-stdio: ["ignore", "pipe", "pipe"],
180-});
181-if (result.status === 0) {
182-return result.stdout.trim() || undefined;
183-}
184-if (options.requireSuccess) {
185-const reason =
186-result.error?.message || result.stderr.trim() || `exit code ${result.status ?? "unknown"}`;
187-throw new Error(`Failed to run ${[command, ...args].join(" ")}: ${reason}`);
188-}
189-return undefined;
190-}
191-192-function getGlobalPackageRoots(
193-method: InstallMethod,
194-_packageName: string,
195-npmCommand?: string[],
196-): string[] {
197-switch (method) {
198-case "npm": {
199-const configured = !!npmCommand?.length;
200-const [command = "npm", ...npmArgs] = npmCommand ?? [];
201-if (configured && command === "bun") {
202-const bunBin = readCommandOutput(command, [...npmArgs, "pm", "bin", "-g"], {
203-requireSuccess: true,
204-});
205-const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
206-if (bunBin) {
207-roots.push(join(dirname(bunBin), "install", "global", "node_modules"));
208-}
209-return roots;
210-}
211-const root = readCommandOutput(command, [...npmArgs, "root", "-g"], {
212-requireSuccess: configured,
213-});
214-const inferred = configured ? undefined : getInferredNpmInstall();
215-return [root, inferred?.root].filter((x): x is string => !!x);
216-}
217-case "pnpm": {
218-const root = readCommandOutput("pnpm", ["root", "-g"]);
219-return root ? [root, dirname(root)] : [];
220-}
221-case "yarn": {
222-const dir = readCommandOutput("yarn", ["global", "dir"]);
223-return dir ? [dir, join(dir, "node_modules")] : [];
224-}
225-case "bun": {
226-const bunBin = readCommandOutput("bun", ["pm", "bin", "-g"]);
227-const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
228-if (bunBin) {
229-roots.push(join(dirname(bunBin), "install", "global", "node_modules"));
230-}
231-return roots;
232-}
233-case "bun-binary":
234-case "unknown":
235-return [];
236-}
237-return [];
238-}
239-240-function normalizeExistingPathForComparison(
241-path: string,
242-resolveSymlinks: boolean,
243-): string | undefined {
244-const resolvedPath = resolve(path);
245-if (!existsSync(resolvedPath)) {
246-return undefined;
247-}
248-let normalizedPath = resolvedPath;
249-if (resolveSymlinks) {
250-try {
251-normalizedPath = realpathSync(resolvedPath);
252-} catch {
253-return undefined;
254-}
255-}
256-if (process.platform === "win32") {
257-normalizedPath = normalizedPath.toLowerCase();
258-}
259-return normalizedPath;
260-}
261-262-function getPathComparisonCandidates(path: string): string[] {
263-return Array.from(
264-new Set(
265-[
266-normalizeExistingPathForComparison(path, false),
267-normalizeExistingPathForComparison(path, true),
268-].filter((candidate): candidate is string => !!candidate),
269-),
270-);
271-}
272-273-function getEntrypointPackageDir(): string | undefined {
274-const entrypoint = process.argv[1];
275-if (!entrypoint) {
276-return undefined;
277-}
278-let dir = dirname(entrypoint);
279-while (dir !== dirname(dir)) {
280-if (existsSync(join(dir, "package.json"))) {
281-return dir;
282-}
283-dir = dirname(dir);
284-}
285-return undefined;
286-}
287-288-function isSelfUpdatePathWritable(): boolean {
289-const packageDir = getPackageDir();
290-try {
291-accessSync(packageDir, constants.W_OK);
292-accessSync(dirname(packageDir), constants.W_OK);
293-return true;
294-} catch {
295-return false;
296-}
297-}
298-299-function isManagedByGlobalPackageManager(
300-method: InstallMethod,
301-packageName: string,
302-npmCommand?: string[],
303-): boolean {
304-const packageDirs = [getPackageDir(), getEntrypointPackageDir()].filter(
305-(dir): dir is string => !!dir,
306-);
307-const packageDirCandidates = packageDirs.flatMap((dir) => getPathComparisonCandidates(dir));
308-return getGlobalPackageRoots(method, packageName, npmCommand).some((root) => {
309-return getPathComparisonCandidates(root).some((normalizedRoot) => {
310-const rootPrefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;
311-return packageDirCandidates.some((packageDir) => packageDir.startsWith(rootPrefix));
312-});
313-});
314-}
315-316-export function getSelfUpdateUnavailableInstruction(
317-packageName: string,
318-npmCommand?: string[],
319-updatePackageName = packageName,
320-): string {
321-const method = detectInstallMethod();
322-if (method === "bun-binary") {
323-return `Download from: https://github.com/openclaw/openclaw/releases/latest`;
324-}
325-const command = getSelfUpdateCommandForMethod(method, packageName, updatePackageName, npmCommand);
326-if (command) {
327-if (
328-isManagedByGlobalPackageManager(method, packageName, npmCommand) &&
329-!isSelfUpdatePathWritable()
330-) {
331-return `This installation is managed by a global ${method} install, but the install path is not writable. Update it yourself with: ${command.display}`;
332-}
333-return `This installation is not managed by a global ${method} install. Update it with the package manager, wrapper, or source checkout that provides it.`;
334-}
335-return `Update ${updatePackageName} using the package manager, wrapper, or source checkout that provides this installation.`;
336-}
337-33822// =============================================================================
33923// Package Asset Paths (shipped with executable)
34024// =============================================================================
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。