mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-13 22:46:39 +00:00
refactor: split slack/discord/session maintenance helpers
This commit is contained in:
39
src/infra/stable-node-path.ts
Normal file
39
src/infra/stable-node-path.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
/**
|
||||
* Homebrew Cellar paths (e.g. /opt/homebrew/Cellar/node/25.7.0/bin/node)
|
||||
* break when Homebrew upgrades Node and removes the old version directory.
|
||||
* Resolve these to a stable Homebrew-managed path that survives upgrades:
|
||||
* - Default formula "node": <prefix>/opt/node/bin/node or <prefix>/bin/node
|
||||
* - Versioned formula "node@22": <prefix>/opt/node@22/bin/node (keg-only)
|
||||
*/
|
||||
export async function resolveStableNodePath(nodePath: string): Promise<string> {
|
||||
const cellarMatch = nodePath.match(/^(.+?)\/Cellar\/([^/]+)\/[^/]+\/bin\/node$/);
|
||||
if (!cellarMatch) {
|
||||
return nodePath;
|
||||
}
|
||||
const prefix = cellarMatch[1]; // e.g. /opt/homebrew
|
||||
const formula = cellarMatch[2]; // e.g. "node" or "node@22"
|
||||
|
||||
// Try the Homebrew opt symlink first — works for both default and versioned formulas.
|
||||
const optPath = `${prefix}/opt/${formula}/bin/node`;
|
||||
try {
|
||||
await fs.access(optPath);
|
||||
return optPath;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
|
||||
// For the default "node" formula, also try the direct bin symlink.
|
||||
if (formula === "node") {
|
||||
const binPath = `${prefix}/bin/node`;
|
||||
try {
|
||||
await fs.access(binPath);
|
||||
return binPath;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
return nodePath;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { pathExists } from "../utils.js";
|
||||
import { resolveStableNodePath } from "./stable-node-path.js";
|
||||
import { runGatewayUpdate } from "./update-runner.js";
|
||||
|
||||
type CommandResponse = { stdout?: string; stderr?: string; code?: number | null };
|
||||
@@ -49,7 +50,7 @@ describe("runGatewayUpdate", () => {
|
||||
// Shared fixtureRoot cleaned up in afterAll.
|
||||
});
|
||||
|
||||
function createStableTagRunner(params: {
|
||||
async function createStableTagRunner(params: {
|
||||
stableTag: string;
|
||||
uiIndexPath: string;
|
||||
onDoctor?: () => Promise<void>;
|
||||
@@ -57,7 +58,8 @@ describe("runGatewayUpdate", () => {
|
||||
}) {
|
||||
const calls: string[] = [];
|
||||
let uiBuildCount = 0;
|
||||
const doctorKey = `${process.execPath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`;
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const doctorKey = `${doctorNodePath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`;
|
||||
|
||||
const runCommand = async (argv: string[]) => {
|
||||
const key = argv.join(" ");
|
||||
@@ -287,15 +289,15 @@ describe("runGatewayUpdate", () => {
|
||||
await setupUiIndex();
|
||||
const stableTag = "v1.0.1-1";
|
||||
const betaTag = "v1.0.0-beta.2";
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const { runner, calls } = createRunner({
|
||||
...buildStableTagResponses(stableTag, { additionalTags: [betaTag] }),
|
||||
"pnpm install": { stdout: "" },
|
||||
"pnpm build": { stdout: "" },
|
||||
"pnpm ui:build": { stdout: "" },
|
||||
[`${process.execPath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`]:
|
||||
{
|
||||
stdout: "",
|
||||
},
|
||||
[`${doctorNodePath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`]: {
|
||||
stdout: "",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runWithRunner(runner, { channel: "beta" });
|
||||
@@ -544,7 +546,7 @@ describe("runGatewayUpdate", () => {
|
||||
const uiIndexPath = await setupUiIndex();
|
||||
|
||||
const stableTag = "v1.0.1-1";
|
||||
const { runCommand, calls, doctorKey, getUiBuildCount } = createStableTagRunner({
|
||||
const { runCommand, calls, doctorKey, getUiBuildCount } = await createStableTagRunner({
|
||||
stableTag,
|
||||
uiIndexPath,
|
||||
onUiBuild: async (count) => {
|
||||
@@ -567,7 +569,7 @@ describe("runGatewayUpdate", () => {
|
||||
const uiIndexPath = await setupUiIndex();
|
||||
|
||||
const stableTag = "v1.0.1-1";
|
||||
const { runCommand } = createStableTagRunner({
|
||||
const { runCommand } = await createStableTagRunner({
|
||||
stableTag,
|
||||
uiIndexPath,
|
||||
onUiBuild: async (count) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { detectPackageManager as detectPackageManagerImpl } from "./detect-packa
|
||||
import { readPackageName, readPackageVersion } from "./package-json.js";
|
||||
import { normalizePackageTagInput } from "./package-tag.js";
|
||||
import { trimLogTail } from "./restart-sentinel.js";
|
||||
import { resolveStableNodePath } from "./stable-node-path.js";
|
||||
import {
|
||||
channelToNpmTag,
|
||||
DEFAULT_PACKAGE_CHANNEL,
|
||||
@@ -766,7 +767,8 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
|
||||
// Use --fix so that doctor auto-strips unknown config keys introduced by
|
||||
// schema changes between versions, preventing a startup validation crash.
|
||||
const doctorArgv = [process.execPath, doctorEntry, "doctor", "--non-interactive", "--fix"];
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const doctorArgv = [doctorNodePath, doctorEntry, "doctor", "--non-interactive", "--fix"];
|
||||
const doctorStep = await runStep(
|
||||
step("openclaw doctor", doctorArgv, gitRoot, { OPENCLAW_UPDATE_IN_PROGRESS: "1" }),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user