mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
fix: report broken worktree source symlinks (#3396)
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
mkdirSync,
|
||||
realpathSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
@@ -344,6 +345,71 @@ describe("setup-worktree", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reports broken local-state symlinks in rejected worktree sources", () => {
|
||||
withSourceAndCurrent((source, current) => {
|
||||
symlinkSync("missing.env.local", join(source, ".env.local"));
|
||||
symlinkSync("missing.convex", join(source, ".convex"));
|
||||
|
||||
expect(() =>
|
||||
findSource(
|
||||
{
|
||||
force: false,
|
||||
from: source,
|
||||
quiet: true,
|
||||
},
|
||||
current,
|
||||
),
|
||||
).toThrow(
|
||||
`Rejected sources:\n- ${source}: .env.local is a broken symlink to missing.env.local; .convex is a broken symlink to missing.convex`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a broken Convex symlink when the source env remains readable", () => {
|
||||
withSourceAndCurrent((source, current) => {
|
||||
writeWorktree(source, "local-clawhub");
|
||||
rmSync(join(source, ".convex"), { force: true, recursive: true });
|
||||
symlinkSync("missing.convex", join(source, ".convex"));
|
||||
|
||||
expect(() =>
|
||||
findSource(
|
||||
{
|
||||
force: false,
|
||||
from: source,
|
||||
quiet: true,
|
||||
},
|
||||
current,
|
||||
),
|
||||
).toThrow(`${source}: .convex is a broken symlink to missing.convex`);
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts a remote source when only its unused Convex symlink is broken", () => {
|
||||
withSourceAndCurrent((source, current) => {
|
||||
writeWorktree(source, "remote-clawhub", {
|
||||
env: {
|
||||
CONVEX_DEPLOYMENT: "dev:remote-clawhub",
|
||||
CONVEX_SITE_URL: null,
|
||||
VITE_CONVEX_SITE_URL: null,
|
||||
VITE_CONVEX_URL: "https://remote-clawhub.convex.cloud",
|
||||
},
|
||||
});
|
||||
rmSync(join(source, ".convex"), { force: true, recursive: true });
|
||||
symlinkSync("missing.convex", join(source, ".convex"));
|
||||
|
||||
expect(
|
||||
findSource(
|
||||
{
|
||||
force: false,
|
||||
from: source,
|
||||
quiet: true,
|
||||
},
|
||||
current,
|
||||
).path,
|
||||
).toBe(source);
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects local sources that point browser HTTP routes at the Convex function port", () => {
|
||||
withSourceAndCurrent((source, current) => {
|
||||
writeWorktree(source, "local-clawhub", {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bun
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, lstatSync, readFileSync, rmSync, symlinkSync } from "node:fs";
|
||||
import { existsSync, lstatSync, readFileSync, readlinkSync, rmSync, symlinkSync } from "node:fs";
|
||||
import { basename, resolve } from "node:path";
|
||||
|
||||
type Options = {
|
||||
@@ -104,6 +104,25 @@ function readSource(path: string): Source | null {
|
||||
};
|
||||
}
|
||||
|
||||
function describeUnavailableSource(path: string, names: readonly (".env.local" | ".convex")[]) {
|
||||
const problems: string[] = [];
|
||||
|
||||
for (const name of names) {
|
||||
const localPath = resolve(path, name);
|
||||
if (existsSync(localPath)) continue;
|
||||
|
||||
try {
|
||||
if (lstatSync(localPath).isSymbolicLink()) {
|
||||
problems.push(`${name} is a broken symlink to ${readlinkSync(localPath)}`);
|
||||
}
|
||||
} catch {
|
||||
// Missing local state is normal; dangling links require operator repair.
|
||||
}
|
||||
}
|
||||
|
||||
return problems.join("; ");
|
||||
}
|
||||
|
||||
function readConvexConfig(convexPath: string) {
|
||||
const configPath = resolve(convexPath, "local/default/config.json");
|
||||
return existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : null;
|
||||
@@ -200,8 +219,22 @@ export function findSource(options: Options, cwd = process.cwd()) {
|
||||
|
||||
const rejected: string[] = [];
|
||||
for (const candidate of candidates) {
|
||||
const unavailableEnv = describeUnavailableSource(candidate, [".env.local"]);
|
||||
if (unavailableEnv) {
|
||||
const unavailableConvex = describeUnavailableSource(candidate, [".convex"]);
|
||||
const unavailable = [unavailableEnv, unavailableConvex].filter(Boolean).join("; ");
|
||||
rejected.push(`${candidate}: ${unavailable}`);
|
||||
continue;
|
||||
}
|
||||
const source = readSource(candidate);
|
||||
if (!source) continue;
|
||||
const unavailableConvex = source.env.CONVEX_DEPLOYMENT?.startsWith("local:")
|
||||
? describeUnavailableSource(candidate, [".convex"])
|
||||
: "";
|
||||
if (unavailableConvex) {
|
||||
rejected.push(`${candidate}: ${unavailableConvex}`);
|
||||
continue;
|
||||
}
|
||||
const invalid = validateSource(source);
|
||||
if (!invalid) return source;
|
||||
rejected.push(`${candidate}: ${invalid}`);
|
||||
|
||||
Reference in New Issue
Block a user