Files
hanzo-dev 9e38e16c64 Eradicate OpenClaw — resolve merge conflicts and finish the rebrand
Three coordinated things:

1. Resolve the committed upstream/main merge conflicts. 94 files were
   sitting with raw <<<<<<< HEAD ... >>>>>>> upstream/main blocks. Every
   conflict had HanzoBot/Bot on HEAD and OpenClaw on upstream. Resolved
   by keeping HEAD everywhere — 540 conflict blocks.

2. Rewrite remaining OpenClaw text refs.
     OPENCLAW   → HANZO_BOT
     OpenClaw   → HanzoBot          (PascalCase type identifiers)
     openclaw   → bot               (codebase convention — botToken,
                                    botUsername etc.; avoids invalid
                                    hyphenated JS identifiers)
     openclaw[_]/openclaw[A-Z] → bot[_]/bot[A-Z]   (compound)
     ai.openclaw.x → ai.hanzo.bot.x  (JVM package path)

3. Delete or rename openclaw-named file paths (116 of them). Dead
   duplicates with a Bot-named canonical from the partial migration:
   deleted. Otherwise renamed.
     ai/openclaw/**           → deleted (ai/hanzo/bot/** canonical)
     Sources/OpenClaw*/       → deleted (Sources/Bot* canonical)
     Tests/OpenClawIPCTests/  → deleted (Tests/BotIPCTests canonical)
     OpenClawKit/             → deleted (BotKit canonical)
     openclaw-tools.*.ts      → deleted (bot-tools.*.ts canonical)
     openclaw-root.ts etc.    → deleted (bot-root.ts canonical)
     types.openclaw.ts        → deleted (types.bot.ts canonical)
     extensions/*/openclaw.plugin.json → renamed hanzo-bot.plugin.json
     docs/start/openclaw.md   → renamed hanzo-bot.md (+ zh-CN)
     docs/assets/openclaw-*.png, whatsapp-openclaw*.jpg → deleted
     scripts/.../openclaw-*   → deleted (bot/hanzo-bot parallels)
     openclaw.mjs             → deleted (hanzo-bot.mjs is package.bin)

Then patched 65 broken JS/TS import strings where hanzo-bot had ended
up inside an import path ('./types.hanzo-bot.js' → './types.bot.js'
etc., since the on-disk filename uses the brand-neutral bot- prefix).

Pre-existing lint debt cleaned up to get oxlint --type-aware to 0/0:
removed the dead i18n test that referenced a path no longer existing;
defined the missing DIDConfig and WalletConfig types in types.base.ts
(they were imported but never declared); dropped unused imports;
collapsed redundant type assertion and a tautological meta.bot lookup
that the openclaw→bot rename made redundant.

The a2ui.bundle.js generated artifact is kept at its pre-rebrand
contents — it gets regenerated by 'pnpm canvas:a2ui:bundle' from
sources I cannot rebuild in this commit; the source side is clean.

Verified: 0 occurrences of openclaw (case-insensitive) in tree source
content, 0 file or directory paths with openclaw in the name; oxlint
--type-aware src test reports 0/0.
2026-05-11 17:56:29 -07:00

264 lines
6.7 KiB
JavaScript

#!/usr/bin/env node
import { spawn, spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { pathToFileURL } from "node:url";
const compiler = "tsdown";
const compilerArgs = ["exec", compiler, "--no-clean"];
export const runNodeWatchedPaths = ["src", "tsconfig.json", "package.json"];
const statMtime = (filePath, fsImpl = fs) => {
try {
return fsImpl.statSync(filePath).mtimeMs;
} catch {
return null;
}
};
const isExcludedSource = (filePath, srcRoot) => {
const relativePath = path.relative(srcRoot, filePath);
if (relativePath.startsWith("..")) {
return false;
}
return (
relativePath.endsWith(".test.ts") ||
relativePath.endsWith(".test.tsx") ||
relativePath.endsWith(`test-helpers.ts`)
);
};
const findLatestMtime = (dirPath, shouldSkip, deps) => {
let latest = null;
const queue = [dirPath];
while (queue.length > 0) {
const current = queue.pop();
if (!current) {
continue;
}
let entries = [];
try {
entries = deps.fs.readdirSync(current, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
queue.push(fullPath);
continue;
}
if (!entry.isFile()) {
continue;
}
if (shouldSkip?.(fullPath)) {
continue;
}
const mtime = statMtime(fullPath, deps.fs);
if (mtime == null) {
continue;
}
if (latest == null || mtime > latest) {
latest = mtime;
}
}
}
return latest;
};
const runGit = (gitArgs, deps) => {
try {
const result = deps.spawnSync("git", gitArgs, {
cwd: deps.cwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
if (result.status !== 0) {
return null;
}
return (result.stdout ?? "").trim();
} catch {
return null;
}
};
const resolveGitHead = (deps) => {
const head = runGit(["rev-parse", "HEAD"], deps);
return head || null;
};
const hasDirtySourceTree = (deps) => {
const output = runGit(
["status", "--porcelain", "--untracked-files=normal", "--", ...runNodeWatchedPaths],
deps,
);
if (output === null) {
return null;
}
return output.length > 0;
};
const readBuildStamp = (deps) => {
const mtime = statMtime(deps.buildStampPath, deps.fs);
if (mtime == null) {
return { mtime: null, head: null };
}
try {
const raw = deps.fs.readFileSync(deps.buildStampPath, "utf8").trim();
if (!raw.startsWith("{")) {
return { mtime, head: null };
}
const parsed = JSON.parse(raw);
const head = typeof parsed?.head === "string" && parsed.head.trim() ? parsed.head.trim() : null;
return { mtime, head };
} catch {
return { mtime, head: null };
}
};
const hasSourceMtimeChanged = (stampMtime, deps) => {
const srcMtime = findLatestMtime(
deps.srcRoot,
(candidate) => isExcludedSource(candidate, deps.srcRoot),
deps,
);
return srcMtime != null && srcMtime > stampMtime;
};
const shouldBuild = (deps) => {
if (deps.env.BOT_FORCE_BUILD === "1") {
return true;
}
const stamp = readBuildStamp(deps);
if (stamp.mtime == null) {
return true;
}
if (statMtime(deps.distEntry, deps.fs) == null) {
return true;
}
for (const filePath of deps.configFiles) {
const mtime = statMtime(filePath, deps.fs);
if (mtime != null && mtime > stamp.mtime) {
return true;
}
}
const currentHead = resolveGitHead(deps);
if (currentHead && !stamp.head) {
return hasSourceMtimeChanged(stamp.mtime, deps);
}
if (currentHead && stamp.head && currentHead !== stamp.head) {
return hasSourceMtimeChanged(stamp.mtime, deps);
}
if (currentHead) {
const dirty = hasDirtySourceTree(deps);
if (dirty === true) {
return true;
}
if (dirty === false) {
return false;
}
}
if (hasSourceMtimeChanged(stamp.mtime, deps)) {
return true;
}
return false;
};
const logRunner = (message, deps) => {
if (deps.env.BOT_RUNNER_LOG === "0") {
return;
}
deps.stderr.write(`[bot] ${message}\n`);
};
const runHanzoBot = async (deps) => {
const nodeProcess = deps.spawn(deps.execPath, ["bot.mjs", ...deps.args], {
cwd: deps.cwd,
env: deps.env,
stdio: "inherit",
});
const res = await new Promise((resolve) => {
nodeProcess.on("exit", (exitCode, exitSignal) => {
resolve({ exitCode, exitSignal });
});
});
if (res.exitSignal) {
return 1;
}
return res.exitCode ?? 1;
};
const writeBuildStamp = (deps) => {
try {
deps.fs.mkdirSync(deps.distRoot, { recursive: true });
const stamp = {
builtAt: Date.now(),
head: resolveGitHead(deps),
};
deps.fs.writeFileSync(deps.buildStampPath, `${JSON.stringify(stamp)}\n`);
} catch (error) {
// Best-effort stamp; still allow the runner to start.
logRunner(`Failed to write build stamp: ${error?.message ?? "unknown error"}`, deps);
}
};
export async function runNodeMain(params = {}) {
const deps = {
spawn: params.spawn ?? spawn,
spawnSync: params.spawnSync ?? spawnSync,
fs: params.fs ?? fs,
stderr: params.stderr ?? process.stderr,
execPath: params.execPath ?? process.execPath,
cwd: params.cwd ?? process.cwd(),
args: params.args ?? process.argv.slice(2),
env: params.env ? { ...params.env } : { ...process.env },
platform: params.platform ?? process.platform,
};
deps.distRoot = path.join(deps.cwd, "dist");
deps.distEntry = path.join(deps.distRoot, "/entry.js");
deps.buildStampPath = path.join(deps.distRoot, ".buildstamp");
deps.srcRoot = path.join(deps.cwd, "src");
deps.configFiles = [path.join(deps.cwd, "tsconfig.json"), path.join(deps.cwd, "package.json")];
if (!shouldBuild(deps)) {
return await runHanzoBot(deps);
}
logRunner("Building TypeScript (dist is stale).", deps);
const buildCmd = deps.platform === "win32" ? "cmd.exe" : "pnpm";
const buildArgs =
deps.platform === "win32" ? ["/d", "/s", "/c", "pnpm", ...compilerArgs] : compilerArgs;
const build = deps.spawn(buildCmd, buildArgs, {
cwd: deps.cwd,
env: deps.env,
stdio: "inherit",
});
const buildRes = await new Promise((resolve) => {
build.on("exit", (exitCode, exitSignal) => resolve({ exitCode, exitSignal }));
});
if (buildRes.exitSignal) {
return 1;
}
if (buildRes.exitCode !== 0 && buildRes.exitCode !== null) {
return buildRes.exitCode;
}
writeBuildStamp(deps);
return await runHanzoBot(deps);
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
void runNodeMain()
.then((code) => process.exit(code))
.catch((err) => {
console.error(err);
process.exit(1);
});
}