perf: faster plugin updates, half-size first bundle, images from the chat attach on Windows
- Chat view: an attached image's path is pasted on its own after the text (promptForPane). claude turns a paste that is only an image path into [Image #n], but left a Windows path inside a paste of text as a plain path. Other files and @-mentions stay in the text as before. - Plugin bootstrap: depsHash covers only what npm installs (no release version), so a version bump no longer reinstalls runtime deps; the client's installed tree is kept while its manifests are unchanged (no npm ci per update); the user-side build runs vite only (tsc -b cost ~25 s / ~900 MB); clientBuild is recorded only after a build that did not fail, so a failed one is retried; npm output goes to runtime/npm-install.log. - Server: a port held by another program ends with a clear message and exit 1 instead of an uncaught 'error' stack trace. - Client: the onboarding wizard is lazy and rendered only before onboarding; usePrefersReducedMotion moved to hooks/ so PetSettings no longer pulls the Sagi chat/pane stack into the entry chunk (884 KB -> 431 KB, gzip 268 -> 133 KB).
This commit is contained in:
+46
-12
@@ -68,7 +68,19 @@ function buildClient({
|
||||
const stdio = logFd === null ? "inherit" : ["ignore", logFd, logFd];
|
||||
try {
|
||||
const src = path.join(rt, "client-src");
|
||||
fs.rmSync(src, { recursive: true, force: true });
|
||||
// The installed tree is kept across updates while the client's manifests
|
||||
// are unchanged (`stamp`, written after a successful install): a release
|
||||
// rarely touches them, and the clean `npm ci` below fetched all of the
|
||||
// ~200 MB toolchain again on every update.
|
||||
const stamp = path.join(src, "node_modules", ".agenthub-deps");
|
||||
const want = clientDepsHash(root);
|
||||
const reuse = readText(stamp) === want;
|
||||
if (reuse) {
|
||||
for (const e of fs.readdirSync(src))
|
||||
if (e !== "node_modules") fs.rmSync(path.join(src, e), { recursive: true, force: true });
|
||||
} else {
|
||||
fs.rmSync(src, { recursive: true, force: true });
|
||||
}
|
||||
fs.cpSync(path.join(root, "client"), src, {
|
||||
recursive: true,
|
||||
filter: (p) => !/[\\/](node_modules|dist)$/.test(p),
|
||||
@@ -101,19 +113,25 @@ function buildClient({
|
||||
// esbuild is the only dependency with an install script, and it finds its
|
||||
// platform binary without it.
|
||||
const npmFlags = ["--no-audit", "--no-fund", "--include=dev", "--ignore-scripts"];
|
||||
if (
|
||||
!exec(NPM, ["ci", ...npmFlags], src, stdio) &&
|
||||
!exec(NPM, ["install", ...npmFlags], src, stdio)
|
||||
) {
|
||||
return "failed";
|
||||
if (!reuse) {
|
||||
if (
|
||||
!exec(NPM, ["ci", ...npmFlags], src, stdio) &&
|
||||
!exec(NPM, ["install", ...npmFlags], src, stdio)
|
||||
) {
|
||||
return "failed";
|
||||
}
|
||||
fs.mkdirSync(path.dirname(stamp), { recursive: true });
|
||||
fs.writeFileSync(stamp, want);
|
||||
}
|
||||
// = client/package.json's "build" script (`tsc -b && vite build`); a test
|
||||
// keeps the two in step.
|
||||
// client/package.json's "build" script is `tsc -b && vite build`; only the
|
||||
// vite half runs here. The type check emits nothing the bundle needs, and on
|
||||
// a user's machine it cost ~25 s and ~900 MB of memory per update — and a
|
||||
// type error in a release then kept the old UI everywhere (v2.7.0). The
|
||||
// release itself still runs `tsc -b`. A test keeps the two in step.
|
||||
const bin = (...p) => path.join(src, "node_modules", ...p);
|
||||
if (
|
||||
!exec(NODE, [bin("typescript", "bin", "tsc"), "-b"], src, stdio) ||
|
||||
!exec(NODE, [bin("vite", "bin", "vite.js"), "build"], src, stdio)
|
||||
) {
|
||||
if (!exec(NODE, [bin("vite", "bin", "vite.js"), "build"], src, stdio)) {
|
||||
// Maybe a damaged kept tree: the next build starts from a clean install.
|
||||
fs.rmSync(stamp, { force: true });
|
||||
return "failed";
|
||||
}
|
||||
|
||||
@@ -125,6 +143,22 @@ function buildClient({
|
||||
}
|
||||
}
|
||||
|
||||
function readText(file) {
|
||||
try {
|
||||
return fs.readFileSync(file, "utf8");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The client's manifests, which decide whether its installed tree still fits. */
|
||||
function clientDepsHash(root) {
|
||||
const h = require("crypto").createHash("sha256");
|
||||
for (const f of ["package.json", "package-lock.json"])
|
||||
h.update(readText(path.join(root, "client", f)) ?? `no ${f}`);
|
||||
return h.digest("hex").slice(0, 16);
|
||||
}
|
||||
|
||||
function dashboardUrl() {
|
||||
try {
|
||||
return `http://localhost:${resolveDashboardPort()}`;
|
||||
|
||||
Reference in New Issue
Block a user