Files
tutti/tools/scripts/prepared-desktop-launch.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

126 lines
3.3 KiB
JavaScript

import { spawn } from "node:child_process";
import { access, readdir, stat } from "node:fs/promises";
import { createRequire } from "node:module";
import { join } from "node:path";
const ignoredDirectories = new Set([
".git",
"build",
"dist",
"node_modules",
"out"
]);
export function isDesktopBundleFresh({ outputMtimeMs, sourceMtimeMs }) {
return (
Number.isFinite(outputMtimeMs) &&
Number.isFinite(sourceMtimeMs) &&
outputMtimeMs >= sourceMtimeMs
);
}
export async function ensurePreparedDesktopLaunch({ workspaceRoot, log }) {
const desktopRoot = join(workspaceRoot, "apps", "desktop");
const outputFiles = [
join(desktopRoot, "out", "main", "index.js"),
join(desktopRoot, "out", "preload", "index.cjs"),
join(desktopRoot, "out", "renderer", "index.html")
];
const sourceMtimeMs = await latestSourceMtimeMs(workspaceRoot);
const outputMtimeMs = await oldestMtimeMs(outputFiles);
if (!isDesktopBundleFresh({ outputMtimeMs, sourceMtimeMs })) {
log("building prepared Desktop bundle");
await runCommand(
"pnpm",
["--filter", "@tutti-os/desktop", "build"],
workspaceRoot
);
} else {
log("reusing prepared Desktop bundle");
}
const electronExecutable = createRequire(join(desktopRoot, "package.json"))(
"electron"
);
return {
args: [desktopRoot],
command: electronExecutable,
launchMode: "prebuilt"
};
}
async function latestSourceMtimeMs(workspaceRoot) {
const sourceRoots = [
join(workspaceRoot, "apps", "desktop"),
join(workspaceRoot, "packages"),
join(workspaceRoot, "config"),
join(workspaceRoot, "package.json"),
join(workspaceRoot, "pnpm-lock.yaml")
];
let latest = 0;
for (const sourceRoot of sourceRoots) {
latest = Math.max(latest, await treeMtimeMs(sourceRoot));
}
return latest;
}
async function treeMtimeMs(path) {
let entry;
try {
entry = await stat(path);
} catch (error) {
if (error?.code === "ENOENT") return 0;
throw error;
}
if (!entry.isDirectory()) return entry.mtimeMs;
let latest = entry.mtimeMs;
for (const child of await readdir(path, { withFileTypes: true })) {
if (child.isDirectory() && ignoredDirectories.has(child.name)) continue;
latest = Math.max(latest, await treeMtimeMs(join(path, child.name)));
}
return latest;
}
async function oldestMtimeMs(paths) {
let oldest = Number.POSITIVE_INFINITY;
for (const path of paths) {
try {
await access(path);
oldest = Math.min(oldest, (await stat(path)).mtimeMs);
} catch (error) {
if (error?.code === "ENOENT") return 0;
throw error;
}
}
return oldest;
}
function runCommand(command, args, cwd) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
cwd,
env: process.env,
stdio: ["ignore", "pipe", "pipe"]
});
child.stdout.resume();
let stderr = "";
child.stderr.on("data", (chunk) => {
stderr += String(chunk);
});
child.once("error", reject);
child.once("exit", (code, signal) => {
if (code === 0) {
resolve();
return;
}
reject(
new Error(
`${command} ${args.join(" ")} failed (${code ?? signal ?? "unknown"})${stderr.trim() ? `: ${stderr.trim()}` : ""}`
)
);
});
});
}