Files
elizaos--eliza/packages/app-core/scripts/sync-desktop-renderer.mjs
T
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

351 lines
8.4 KiB
JavaScript

#!/usr/bin/env node
/** Supports app-core build, packaging, or development orchestration for sync desktop renderer mjs. */
import { spawn, spawnSync } from "node:child_process";
import { cpSync, existsSync, mkdirSync, statSync, watch } from "node:fs";
import path from "node:path";
import { resolveMainAppDir } from "./lib/app-dir.mjs";
import { signalSpawnedProcessTree } from "./lib/kill-process-tree.mjs";
import { resolveRepoRootFromImportMeta } from "./lib/repo-root.mjs";
const ROOT = resolveRepoRootFromImportMeta(import.meta.url, {
fallbackToCwd: true,
});
const ARGS = process.argv.slice(2);
const APP_DIR = resolveMainAppDir(ROOT, "app");
const DIST_DIR = path.join(APP_DIR, "dist");
const CLEANUP_HELPER_SCRIPT = resolveCleanupHelperScript();
const APP_PATH_ARG =
getArgValue("app-path") || process.env.ELIZA_DESKTOP_APP_PATH || null;
const RENDERER_PATH_ARG =
getArgValue("renderer-path") ||
process.env.ELIZA_DESKTOP_RENDERER_PATH ||
null;
const SKIP_BUILD = getBooleanArg("skip-build") || getBooleanArg("no-build");
const WATCH = getBooleanArg("watch");
const DEFAULT_APP_PATH = "/Applications/Eliza-canary.app";
const DEFAULT_RENDERER_DIR = path.join(
resolveAppPath(APP_PATH_ARG || DEFAULT_APP_PATH),
"Contents",
"Resources",
"app",
"renderer",
);
const TARGET_RENDERER = RENDERER_PATH_ARG
? path.resolve(RENDERER_PATH_ARG)
: DEFAULT_RENDERER_DIR;
const BUILD_ENV = { ...process.env };
let syncTimer = null;
let rendererWatcher = null;
let activeBuildProcess = null;
const WATCH_SYNC_DEBOUNCE_MS = 250;
function resolveCleanupHelperScript() {
const candidates = [
path.join(ROOT, "packages", "scripts", "rm-path-recursive.mjs"),
path.join(ROOT, "eliza", "packages", "scripts", "rm-path-recursive.mjs"),
];
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
}
function removePathRecursive(targetPath) {
if (!existsSync(targetPath)) {
return;
}
const result = spawnSync(
"node",
[CLEANUP_HELPER_SCRIPT, path.relative(ROOT, targetPath)],
{
cwd: ROOT,
encoding: "utf8",
stdio: "pipe",
},
);
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
const output = [result.stdout, result.stderr]
.filter(Boolean)
.join("\n")
.trim();
throw new Error(
`cleanup helper failed for ${targetPath}${output ? `:\n${output}` : ""}`,
);
}
}
function fail(message, code = 1) {
console.error(`[eliza-ui-sync] ${message}`);
process.exit(code);
}
function which(command) {
const parts = process.env.PATH ? process.env.PATH.split(path.delimiter) : [];
for (const part of parts) {
const candidate = path.join(part, command);
if (existsSync(candidate)) return candidate;
if (process.platform === "win32") {
const exe = `${candidate}.exe`;
if (existsSync(exe)) return exe;
}
}
return null;
}
function resolveRunner() {
return which("bunx") || which("npx");
}
function resolveAppPath(input) {
const expanded = input.startsWith("~")
? path.join(
process.env.HOME ?? process.env.USERPROFILE ?? "",
input.slice(1),
)
: input;
return path.resolve(expanded);
}
function ensureAppDir() {
if (!existsSync(APP_DIR)) {
fail(`Expected app dir at ${APP_DIR}`);
}
}
function scheduleSync() {
if (syncTimer) clearTimeout(syncTimer);
syncTimer = setTimeout(() => {
try {
syncRendererBundle();
} catch (error) {
console.error(
`[eliza-ui-sync] sync failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}, WATCH_SYNC_DEBOUNCE_MS);
}
function startRendererWatcher() {
if (rendererWatcher) return;
if (!existsSync(DIST_DIR)) {
return;
}
rendererWatcher = watch(DIST_DIR, { recursive: true }, () => {
scheduleSync();
});
}
function waitForDistAndWatch() {
const poll = setInterval(() => {
if (!existsSync(DIST_DIR)) {
return;
}
clearInterval(poll);
startRendererWatcher();
scheduleSync();
}, 300);
}
function stopWatchers() {
if (syncTimer) {
clearTimeout(syncTimer);
syncTimer = null;
}
if (rendererWatcher) {
rendererWatcher.close();
rendererWatcher = null;
}
if (activeBuildProcess) {
activeBuildProcess.removeAllListeners();
signalSpawnedProcessTree(activeBuildProcess, "SIGTERM");
activeBuildProcess = null;
}
}
function cleanup(code = 0) {
stopWatchers();
process.exit(code);
}
function hasArg(name) {
return ARGS.includes(`--${name}`);
}
function getArgValue(name) {
const eqPrefix = `--${name}=`;
const exactIndex = ARGS.indexOf(`--${name}`);
if (exactIndex >= 0) {
const value = ARGS[exactIndex + 1];
if (!value || value.startsWith("--")) {
return null;
}
return value;
}
const eqArg = ARGS.find((item) => item.startsWith(eqPrefix));
if (eqArg) {
return eqArg.slice(eqPrefix.length);
}
return null;
}
function getBooleanArg(name) {
const value = getArgValue(name);
if (value !== null) {
const normalized = value.toLowerCase();
return (
normalized === "1" ||
normalized === "true" ||
normalized === "yes" ||
normalized === "on"
);
}
return hasArg(name);
}
function runCommand(args) {
const runner = resolveRunner();
if (!runner) fail("Could not find bunx or npx in PATH.");
const result = spawnSync(runner, args, {
cwd: APP_DIR,
stdio: "inherit",
env: BUILD_ENV,
encoding: "utf8",
});
if (result.status !== 0) {
fail(
`${runner} ${args.join(" ")} failed with exit code ${result.status}.`,
result.status ?? 1,
);
}
}
function runBuild() {
runCommand(["vite", "build"]);
}
function runBuildWatch() {
const runner = resolveRunner();
if (!runner) fail("Could not find bunx or npx in PATH.");
const proc = spawn(runner, ["vite", "build", "--watch"], {
cwd: APP_DIR,
env: BUILD_ENV,
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
});
proc.stdout.on("data", (chunk) => {
const text = chunk.toString();
process.stdout.write(text);
if (text.includes("built in") || text.includes("Build completed")) {
scheduleSync();
}
});
proc.stderr.on("data", (chunk) => {
process.stderr.write(chunk);
});
proc.on("exit", (code) => {
if (code !== 0) {
console.error(`[eliza-ui-sync] vite watch exited with code ${code}`);
cleanup(code ?? 1);
}
});
return proc;
}
function syncRendererBundle() {
if (!existsSync(DIST_DIR)) {
fail(`Build output missing: ${DIST_DIR}`);
}
try {
const stat = statSync(TARGET_RENDERER);
if (!stat.isDirectory()) {
fail(`Target renderer path is not a directory: ${TARGET_RENDERER}`);
}
} catch {
// Renderer path will be created below.
}
removePathRecursive(TARGET_RENDERER);
mkdirSync(TARGET_RENDERER, { recursive: true });
cpSync(DIST_DIR, TARGET_RENDERER, { recursive: true, force: true });
console.log(`[eliza-ui-sync] Synced ${DIST_DIR} -> ${TARGET_RENDERER}`);
}
function printUsage() {
console.log(
"Usage: node scripts/sync-desktop-renderer.mjs [--app-path <path>] [--renderer-path <path>] [--skip-build] [--watch]",
);
console.log("Environment:");
console.log(
" ELIZA_DESKTOP_APP_PATH Override /Applications/Eliza-canary.app",
);
console.log(
" ELIZA_DESKTOP_RENDERER_PATH Override renderer folder directly",
);
console.log("\nExamples:");
console.log(" node scripts/sync-desktop-renderer.mjs");
console.log(
" node scripts/sync-desktop-renderer.mjs --app-path ~/Applications/Eliza.app --skip-build",
);
console.log(" node scripts/sync-desktop-renderer.mjs --watch");
}
function main() {
if (process.platform !== "darwin") {
console.log(
"[eliza-ui-sync] Warning: this script targets macOS app bundles by default.",
);
}
if (hasArg("help") || hasArg("h")) {
printUsage();
process.exit(0);
}
ensureAppDir();
process.on("SIGINT", () => cleanup(0));
process.on("SIGTERM", () => cleanup(0));
if (WATCH) {
if (SKIP_BUILD) {
console.log(
"[eliza-ui-sync] --skip-build set: watching built dist folder only",
);
waitForDistAndWatch();
return;
}
activeBuildProcess = runBuildWatch();
waitForDistAndWatch();
return;
}
if (SKIP_BUILD) {
console.log(
"[eliza-ui-sync] --skip-build set: syncing existing dist folder only",
);
} else {
runBuild();
}
syncRendererBundle();
}
main();