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
351 lines
8.4 KiB
JavaScript
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();
|