@agentuity/cli 1.0.59 → 1.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cmd/build/vite/static-render-worker.d.ts +4 -0
  2. package/dist/cmd/build/vite/static-render-worker.d.ts.map +1 -0
  3. package/dist/cmd/build/vite/static-render-worker.js +58 -0
  4. package/dist/cmd/build/vite/static-render-worker.js.map +1 -0
  5. package/dist/cmd/build/vite/vite-build-worker.d.ts +2 -0
  6. package/dist/cmd/build/vite/vite-build-worker.d.ts.map +1 -0
  7. package/dist/cmd/build/vite/vite-build-worker.js +50 -0
  8. package/dist/cmd/build/vite/vite-build-worker.js.map +1 -0
  9. package/dist/cmd/build/vite/vite-builder.d.ts +1 -0
  10. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/vite-builder.js +261 -23
  12. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  13. package/dist/cmd/cloud/deploy-fork.d.ts +10 -0
  14. package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -1
  15. package/dist/cmd/cloud/deploy-fork.js +41 -23
  16. package/dist/cmd/cloud/deploy-fork.js.map +1 -1
  17. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  18. package/dist/cmd/cloud/deploy.js +53 -11
  19. package/dist/cmd/cloud/deploy.js.map +1 -1
  20. package/dist/cmd/project/show.d.ts.map +1 -1
  21. package/dist/cmd/project/show.js +9 -0
  22. package/dist/cmd/project/show.js.map +1 -1
  23. package/dist/cmd/support/report.d.ts.map +1 -1
  24. package/dist/cmd/support/report.js +19 -10
  25. package/dist/cmd/support/report.js.map +1 -1
  26. package/dist/steps.d.ts.map +1 -1
  27. package/dist/steps.js +38 -0
  28. package/dist/steps.js.map +1 -1
  29. package/dist/tui.d.ts.map +1 -1
  30. package/dist/tui.js +6 -4
  31. package/dist/tui.js.map +1 -1
  32. package/dist/utils/zip.d.ts.map +1 -1
  33. package/dist/utils/zip.js +19 -10
  34. package/dist/utils/zip.js.map +1 -1
  35. package/package.json +8 -8
  36. package/src/cmd/build/vite/static-render-worker.ts +72 -0
  37. package/src/cmd/build/vite/vite-build-worker.ts +58 -0
  38. package/src/cmd/build/vite/vite-builder.ts +295 -23
  39. package/src/cmd/cloud/deploy-fork.ts +56 -22
  40. package/src/cmd/cloud/deploy.ts +68 -12
  41. package/src/cmd/project/show.ts +9 -0
  42. package/src/cmd/support/report.ts +21 -10
  43. package/src/steps.ts +38 -0
  44. package/src/tui.ts +6 -4
  45. package/src/utils/zip.ts +22 -10
package/dist/utils/zip.js CHANGED
@@ -1,10 +1,21 @@
1
- import { readFileSync, lstatSync } from 'node:fs';
2
- import { relative } from 'node:path';
1
+ import { createWriteStream, lstatSync } from 'node:fs';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { dirname, relative } from 'node:path';
3
4
  import { Glob } from 'bun';
4
- import AdmZip from 'adm-zip';
5
+ import archiver from 'archiver';
5
6
  import { toForwardSlash } from './normalize-path';
6
7
  export async function zipDir(dir, outdir, options) {
7
- const zip = new AdmZip();
8
+ await mkdir(dirname(outdir), { recursive: true });
9
+ const output = createWriteStream(outdir);
10
+ const zip = archiver('zip', {
11
+ zlib: { level: 9 },
12
+ });
13
+ const writeDone = new Promise((resolve, reject) => {
14
+ output.on('close', resolve);
15
+ output.on('error', reject);
16
+ zip.on('error', reject);
17
+ });
18
+ zip.pipe(output);
8
19
  const files = await Array.fromAsync(new Glob('**/*').scan({ cwd: dir, absolute: true, dot: true, followSymlinks: false }));
9
20
  const total = files.length;
10
21
  let count = 0;
@@ -23,11 +34,8 @@ export async function zipDir(dir, outdir, options) {
23
34
  // across machines and would cause EISDIR errors on extraction.
24
35
  const stat = lstatSync(file);
25
36
  if (!stat.isSymbolicLink() && !stat.isDirectory()) {
26
- // Use addFile with explicit Unix permissions (0o644) instead of addLocalFile.
27
- // On Windows, addLocalFile relies on OS file stats which may produce zip entries
28
- // with incorrect Unix permission bits, causing EACCES errors when extracted on Linux.
29
- const data = readFileSync(file);
30
- zip.addFile(rel, data, '', 0o644);
37
+ // Set explicit Unix permissions (0o644) for portability across OSes.
38
+ zip.file(file, { name: rel, mode: 0o644 });
31
39
  }
32
40
  }
33
41
  catch (err) {
@@ -41,7 +49,8 @@ export async function zipDir(dir, outdir, options) {
41
49
  await Bun.sleep(10); // give some time for the progress bar to render
42
50
  }
43
51
  }
44
- await zip.writeZip(outdir);
52
+ await zip.finalize();
53
+ await writeDone;
45
54
  if (options?.progress) {
46
55
  options.progress(100);
47
56
  await Bun.sleep(100); // give some time for the progress bar to render
@@ -1 +1 @@
1
- {"version":3,"file":"zip.js","sourceRoot":"","sources":["../../src/utils/zip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAW,EAAE,MAAc,EAAE,OAAiB;IAC1E,MAAM,GAAG,GAAG,IAAI,MAAM,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,SAAS,CAClC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CACrF,CAAC;IACF,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QAChD,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;gBAChC,IAAI,GAAG,IAAI,CAAC;YACb,CAAC;QACF,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,IAAI,CAAC;gBACJ,mEAAmE;gBACnE,mEAAmE;gBACnE,+DAA+D;gBAC/D,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;oBACnD,8EAA8E;oBAC9E,iFAAiF;oBACjF,sFAAsF;oBACtF,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;oBAChC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;gBACnC,CAAC;YACF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,KAAK,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAChF,CAAC;QACF,CAAC;QACD,KAAK,EAAE,CAAC;QACR,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;YACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC3B,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,gDAAgD;QACtE,CAAC;IACF,CAAC;IACD,MAAM,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,gDAAgD;IACvE,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"zip.js","sourceRoot":"","sources":["../../src/utils/zip.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentuity/cli",
3
- "version": "1.0.59",
3
+ "version": "1.0.60",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Agentuity employees and contributors",
6
6
  "type": "module",
@@ -41,13 +41,13 @@
41
41
  "prepublishOnly": "bun run clean && bun run build"
42
42
  },
43
43
  "dependencies": {
44
- "@agentuity/auth": "1.0.59",
45
- "@agentuity/core": "1.0.59",
46
- "@agentuity/server": "1.0.59",
44
+ "@agentuity/auth": "1.0.60",
45
+ "@agentuity/core": "1.0.60",
46
+ "@agentuity/server": "1.0.60",
47
47
  "@datasert/cronjs-parser": "^1.4.0",
48
48
  "@vitejs/plugin-react": "^5.1.2",
49
49
  "acorn-loose": "^8.5.2",
50
- "adm-zip": "^0.5.16",
50
+ "archiver": "^7.0.1",
51
51
  "astring": "^1.9.0",
52
52
  "cli-table3": "^0.6.5",
53
53
  "commander": "^14.0.2",
@@ -59,11 +59,11 @@
59
59
  "typescript": "^5.9.0",
60
60
  "vite": "^7.2.7",
61
61
  "zod": "^4.3.5",
62
- "@agentuity/frontend": "1.0.59"
62
+ "@agentuity/frontend": "1.0.60"
63
63
  },
64
64
  "devDependencies": {
65
- "@agentuity/test-utils": "1.0.59",
66
- "@types/adm-zip": "^0.5.7",
65
+ "@agentuity/test-utils": "1.0.60",
66
+ "@types/archiver": "^6.0.3",
67
67
  "@types/bun": "latest",
68
68
  "@types/tar-fs": "^2.0.4",
69
69
  "bun-plugin-tailwind": "^0.1.2",
@@ -0,0 +1,72 @@
1
+ import { format } from 'node:util';
2
+ import type { Logger } from '../../../types';
3
+ import { runStaticRender } from './static-renderer';
4
+
5
+ function createWorkerLogger(): Logger {
6
+ const write = (writer: (...args: unknown[]) => void, args: unknown[]) => {
7
+ writer(format(...args));
8
+ };
9
+ let loggerRef: Logger;
10
+
11
+ loggerRef = {
12
+ trace: (...args: unknown[]) => write(console.debug, args),
13
+ debug: (...args: unknown[]) => write(console.debug, args),
14
+ info: (...args: unknown[]) => write(console.log, args),
15
+ warn: (...args: unknown[]) => write(console.warn, args),
16
+ error: (...args: unknown[]) => write(console.error, args),
17
+ child: () => loggerRef,
18
+ fatal: (...args: unknown[]) => {
19
+ const message = format(...args);
20
+ console.error(message);
21
+ throw new Error(message);
22
+ },
23
+ };
24
+
25
+ return loggerRef;
26
+ }
27
+
28
+ export interface StaticRenderWorkerOptions {
29
+ rootDir: string;
30
+ }
31
+
32
+ async function main(): Promise<void> {
33
+ const optionsPath = process.argv[2];
34
+ if (!optionsPath) {
35
+ throw new Error('Missing worker options file path argument');
36
+ }
37
+
38
+ const optionsFile = Bun.file(optionsPath);
39
+ if (!(await optionsFile.exists())) {
40
+ throw new Error(`Worker options file does not exist: ${optionsPath}`);
41
+ }
42
+
43
+ const options = JSON.parse(await optionsFile.text()) as StaticRenderWorkerOptions;
44
+ const logger = createWorkerLogger();
45
+
46
+ // Load user plugins from agentuity.config.ts (can't serialize plugin functions)
47
+ const { loadAgentuityConfig } = await import('./config-loader');
48
+ const config = await loadAgentuityConfig(options.rootDir, logger);
49
+ const userPlugins = config?.plugins || [];
50
+
51
+ const result = await runStaticRender({
52
+ rootDir: options.rootDir,
53
+ logger,
54
+ userPlugins,
55
+ });
56
+
57
+ // Write result to stdout for parent to parse
58
+ console.log(JSON.stringify(result));
59
+ }
60
+
61
+ void main()
62
+ .then(() => {
63
+ process.exit(0);
64
+ })
65
+ .catch((error) => {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ console.error(`[static-render-worker] ${message}`);
68
+ if (error instanceof Error && error.stack) {
69
+ console.error(error.stack);
70
+ }
71
+ process.exit(1);
72
+ });
@@ -0,0 +1,58 @@
1
+ import { format } from 'node:util';
2
+ import type { Logger } from '../../../types';
3
+ import { runViteBuild, type ViteBuildWorkerOptions } from './vite-builder';
4
+
5
+ function createWorkerLogger(): Logger {
6
+ const write = (writer: (...args: unknown[]) => void, args: unknown[]) => {
7
+ writer(format(...args));
8
+ };
9
+ let loggerRef: Logger;
10
+
11
+ loggerRef = {
12
+ trace: (...args: unknown[]) => write(console.debug, args),
13
+ debug: (...args: unknown[]) => write(console.debug, args),
14
+ info: (...args: unknown[]) => write(console.log, args),
15
+ warn: (...args: unknown[]) => write(console.warn, args),
16
+ error: (...args: unknown[]) => write(console.error, args),
17
+ child: () => loggerRef,
18
+ fatal: (...args: unknown[]) => {
19
+ const message = format(...args);
20
+ console.error(message);
21
+ throw new Error(message);
22
+ },
23
+ };
24
+
25
+ return loggerRef;
26
+ }
27
+
28
+ async function main(): Promise<void> {
29
+ const optionsPath = process.argv[2];
30
+ if (!optionsPath) {
31
+ throw new Error('Missing worker options file path argument');
32
+ }
33
+
34
+ const optionsFile = Bun.file(optionsPath);
35
+ if (!(await optionsFile.exists())) {
36
+ throw new Error(`Worker options file does not exist: ${optionsPath}`);
37
+ }
38
+
39
+ const options = JSON.parse(await optionsFile.text()) as ViteBuildWorkerOptions;
40
+
41
+ await runViteBuild({
42
+ ...options,
43
+ logger: createWorkerLogger(),
44
+ });
45
+ }
46
+
47
+ void main()
48
+ .then(() => {
49
+ process.exit(0);
50
+ })
51
+ .catch((error) => {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ console.error(`[vite-build-worker] ${message}`);
54
+ if (error instanceof Error && error.stack) {
55
+ console.error(error.stack);
56
+ }
57
+ process.exit(1);
58
+ });
@@ -6,7 +6,11 @@
6
6
 
7
7
  import { join } from 'node:path';
8
8
  import { existsSync, renameSync, rmSync } from 'node:fs';
9
+ import { randomUUID } from 'node:crypto';
9
10
  import { createRequire } from 'node:module';
11
+ import { tmpdir } from 'node:os';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { StructuredError } from '@agentuity/core';
10
14
  import type { InlineConfig, Plugin } from 'vite';
11
15
  import type { Logger, DeployOptions } from '../../../types';
12
16
  import { browserEnvPlugin } from './browser-env-plugin';
@@ -15,6 +19,8 @@ import { beaconPlugin } from './beacon-plugin';
15
19
  import { publicAssetPathPlugin } from './public-asset-path-plugin';
16
20
  import type { BuildReportCollector } from '../../../build-report';
17
21
 
22
+ const BuildFailedError = StructuredError('BuildFailedError');
23
+
18
24
  /**
19
25
  * Vite plugin to flatten the output structure for index.html
20
26
  *
@@ -70,6 +76,232 @@ export interface ViteBuildOptions {
70
76
  profile?: string;
71
77
  }
72
78
 
79
+ export type ViteBuildWorkerOptions = Omit<ViteBuildOptions, 'logger' | 'collector'>;
80
+
81
+ /**
82
+ * Drain a subprocess stream, forwarding each chunk to the callback
83
+ * without accumulating in memory. Only the last `tailBytes` of output
84
+ * are retained so we can include them in error messages on failure.
85
+ */
86
+ async function drainSubprocessStream(
87
+ stream: ReadableStream<Uint8Array>,
88
+ onChunk: (chunk: string) => void,
89
+ tailBytes = 4096
90
+ ): Promise<string> {
91
+ const reader = stream.getReader();
92
+ const decoder = new TextDecoder();
93
+ // Ring buffer that keeps only the trailing output for error context
94
+ let tail = '';
95
+
96
+ while (true) {
97
+ const { done, value } = await reader.read();
98
+ if (done) break;
99
+ const text = decoder.decode(value, { stream: true });
100
+ if (!text) continue;
101
+ onChunk(text);
102
+ tail = (tail + text).slice(-tailBytes);
103
+ }
104
+
105
+ const finalText = decoder.decode();
106
+ if (finalText) {
107
+ onChunk(finalText);
108
+ tail = (tail + finalText).slice(-tailBytes);
109
+ }
110
+
111
+ return tail;
112
+ }
113
+
114
+ /**
115
+ * Detect actually available memory for subprocess heap sizing.
116
+ * Reads cgroup v2/v1 max and current usage to compute free memory.
117
+ * Falls back to os.freemem() if cgroup files aren't available.
118
+ */
119
+ async function detectAvailableMemory(
120
+ logger: Logger,
121
+ label: string
122
+ ): Promise<Record<string, string>> {
123
+ let cgroupMaxBytes = 0;
124
+ let cgroupCurrentBytes = 0;
125
+ let availableBytes = 0;
126
+ try {
127
+ if (process.platform === 'linux') {
128
+ // Try cgroup v2
129
+ const cgroupMax = Bun.file('/sys/fs/cgroup/memory.max');
130
+ const cgroupCurrent = Bun.file('/sys/fs/cgroup/memory.current');
131
+ if ((await cgroupMax.exists()) && (await cgroupCurrent.exists())) {
132
+ const maxStr = (await cgroupMax.text()).trim();
133
+ const currentStr = (await cgroupCurrent.text()).trim();
134
+ // "max" means unlimited — use total system memory
135
+ if (maxStr === 'max') {
136
+ const { totalmem } = await import('node:os');
137
+ cgroupMaxBytes = totalmem();
138
+ } else if (/^\d+$/.test(maxStr)) {
139
+ cgroupMaxBytes = Number(maxStr);
140
+ }
141
+ if (/^\d+$/.test(currentStr)) {
142
+ cgroupCurrentBytes = Number(currentStr);
143
+ }
144
+ if (cgroupMaxBytes > 0) {
145
+ availableBytes =
146
+ cgroupCurrentBytes > 0 ? cgroupMaxBytes - cgroupCurrentBytes : cgroupMaxBytes;
147
+ }
148
+ }
149
+ // Fallback to cgroup v1
150
+ if (!availableBytes) {
151
+ const v1Limit = Bun.file('/sys/fs/cgroup/memory/memory.limit_in_bytes');
152
+ const v1Usage = Bun.file('/sys/fs/cgroup/memory/memory.usage_in_bytes');
153
+ if ((await v1Limit.exists()) && (await v1Usage.exists())) {
154
+ const limitStr = (await v1Limit.text()).trim();
155
+ const usageStr = (await v1Usage.text()).trim();
156
+ if (/^\d+$/.test(limitStr) && /^\d+$/.test(usageStr)) {
157
+ cgroupMaxBytes = Number(limitStr);
158
+ cgroupCurrentBytes = Number(usageStr);
159
+ availableBytes = cgroupMaxBytes - cgroupCurrentBytes;
160
+ }
161
+ }
162
+ }
163
+ }
164
+ if (!availableBytes) {
165
+ const { freemem } = await import('node:os');
166
+ availableBytes = freemem();
167
+ }
168
+ } catch {
169
+ // If detection fails, let JSC use its own defaults
170
+ }
171
+
172
+ const jscEnv: Record<string, string> = {};
173
+ if (availableBytes > 0) {
174
+ const maxMb = Math.round(cgroupMaxBytes / 1024 / 1024);
175
+ const usedMb = Math.round(cgroupCurrentBytes / 1024 / 1024);
176
+ const availMb = Math.round(availableBytes / 1024 / 1024);
177
+ const maxHeap = Math.round(availableBytes * 0.8);
178
+ const maxHeapMb = Math.round(maxHeap / 1024 / 1024);
179
+ jscEnv.BUN_JSC_forceRAMSize = String(availableBytes);
180
+ jscEnv.BUN_JSC_gcMaxHeapSize = String(maxHeap);
181
+ logger.debug(
182
+ `[${label}] Memory: cgroup max=${maxMb} MiB, used=${usedMb} MiB, available=${availMb} MiB, gcMaxHeapSize=${maxHeapMb} MiB`
183
+ );
184
+ } else {
185
+ logger.debug(`[${label}] Could not detect available memory, using JSC defaults`);
186
+ }
187
+ return jscEnv;
188
+ }
189
+
190
+ function resolveWorkerScript(name: string): string {
191
+ const tsPath = fileURLToPath(new URL(`./${name}.ts`, import.meta.url));
192
+ const jsPath = fileURLToPath(new URL(`./${name}.js`, import.meta.url));
193
+ return existsSync(tsPath) ? tsPath : jsPath;
194
+ }
195
+
196
+ /**
197
+ * Spawn an isolated worker subprocess with JSC memory tuning.
198
+ * Returns the captured stdout tail (for parsing results) and throws on failure.
199
+ */
200
+ async function spawnIsolatedWorker(opts: {
201
+ workerName: string;
202
+ label: string;
203
+ optionsJson: unknown;
204
+ cwd: string;
205
+ logger: Logger;
206
+ }): Promise<string> {
207
+ const { workerName, label, optionsJson, cwd, logger } = opts;
208
+ const workerScript = resolveWorkerScript(workerName);
209
+ const optionsPath = join(tmpdir(), `agentuity-${label}-${randomUUID()}.json`);
210
+
211
+ try {
212
+ await Bun.write(optionsPath, JSON.stringify(optionsJson));
213
+ const jscEnv = await detectAvailableMemory(logger, label);
214
+
215
+ const proc = Bun.spawn({
216
+ cmd: [process.execPath, workerScript, optionsPath],
217
+ cwd,
218
+ env: { ...process.env, ...jscEnv },
219
+ stdin: 'ignore',
220
+ stdout: 'pipe',
221
+ stderr: 'pipe',
222
+ });
223
+
224
+ const stdoutPromise =
225
+ proc.stdout && typeof proc.stdout !== 'number'
226
+ ? drainSubprocessStream(proc.stdout, (chunk) => {
227
+ const text = chunk.trim();
228
+ if (text) logger.debug(`[${label}] ${text}`);
229
+ })
230
+ : Promise.resolve('');
231
+
232
+ const stderrPromise =
233
+ proc.stderr && typeof proc.stderr !== 'number'
234
+ ? drainSubprocessStream(proc.stderr, (chunk) => {
235
+ const text = chunk.trim();
236
+ if (text) logger.error(`[${label}] ${text}`);
237
+ })
238
+ : Promise.resolve('');
239
+
240
+ const [stdout, stderr, exitCode] = await Promise.all([
241
+ stdoutPromise,
242
+ stderrPromise,
243
+ proc.exited,
244
+ ]);
245
+
246
+ if (exitCode !== 0) {
247
+ const errorOutput = stderr.trim() || stdout.trim();
248
+ const suffix = errorOutput ? `: ${errorOutput}` : '';
249
+ throw new BuildFailedError({
250
+ message: `${label} failed with exit code ${exitCode}${suffix}`,
251
+ });
252
+ }
253
+ return stdout;
254
+ } catch (error) {
255
+ if (error instanceof Error && error.name === 'BuildFailedError') throw error;
256
+ throw new BuildFailedError({
257
+ message: `Failed to run ${label}: ${error instanceof Error ? error.message : String(error)}`,
258
+ });
259
+ } finally {
260
+ rmSync(optionsPath, { force: true });
261
+ }
262
+ }
263
+
264
+ async function runViteBuildInSubprocess(
265
+ options: ViteBuildWorkerOptions,
266
+ logger: Logger
267
+ ): Promise<void> {
268
+ await spawnIsolatedWorker({
269
+ workerName: 'vite-build-worker',
270
+ label: `vite-build-worker:${options.mode}`,
271
+ optionsJson: options,
272
+ cwd: options.rootDir,
273
+ logger,
274
+ });
275
+ }
276
+
277
+ async function runStaticRenderInSubprocess(
278
+ rootDir: string,
279
+ logger: Logger
280
+ ): Promise<{ routes: number; duration: number }> {
281
+ const stdout = await spawnIsolatedWorker({
282
+ workerName: 'static-render-worker',
283
+ label: 'static-render-worker',
284
+ optionsJson: { rootDir },
285
+ cwd: rootDir,
286
+ logger,
287
+ });
288
+
289
+ // Worker writes JSON result as last line of stdout
290
+ try {
291
+ // Find the last JSON line in the tail
292
+ const lines = stdout.split('\n').filter((l) => l.trim());
293
+ for (let i = lines.length - 1; i >= 0; i--) {
294
+ const line = lines[i]!.trim();
295
+ if (line.startsWith('{')) {
296
+ return JSON.parse(line);
297
+ }
298
+ }
299
+ } catch {
300
+ // Parse failure — return defaults
301
+ }
302
+ return { routes: 0, duration: 0 };
303
+ }
304
+
73
305
  /**
74
306
  * Run a Vite build for the specified mode
75
307
  * Uses inline Vite config (customizable via agentuity.config.ts)
@@ -256,6 +488,9 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
256
488
  // Copy public files to output for CDN upload (production builds only)
257
489
  // In dev mode, Vite serves them directly from src/web/public/
258
490
  copyPublicDir: !dev,
491
+ // Skip compressed size reporting to save memory — we measure
492
+ // sizes during the asset upload phase instead.
493
+ reportCompressedSize: false,
259
494
  },
260
495
  logLevel: isViteDebug ? 'info' : 'warn',
261
496
  };
@@ -322,6 +557,7 @@ interface BuildResult {
322
557
  */
323
558
  export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Promise<BuildResult> {
324
559
  const { rootDir, projectId = '', dev = false, logger, collector } = options;
560
+ const { logger: _logger, collector: _collector, ...workerBaseOptions } = options;
325
561
 
326
562
  if (!dev) {
327
563
  rmSync(join(rootDir, '.agentuity'), { force: true, recursive: true });
@@ -393,13 +629,26 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
393
629
  logger.debug('Building client assets...');
394
630
  const endClientDiagnostic = collector?.startDiagnostic('client-build');
395
631
  const started = Date.now();
396
- await runViteBuild({
397
- ...options,
398
- mode: 'client',
399
- workbenchEnabled: workbenchConfig.enabled,
400
- workbenchRoute: workbenchConfig.route,
401
- analyticsEnabled,
402
- });
632
+ if (dev) {
633
+ await runViteBuild({
634
+ ...options,
635
+ mode: 'client',
636
+ workbenchEnabled: workbenchConfig.enabled,
637
+ workbenchRoute: workbenchConfig.route,
638
+ analyticsEnabled,
639
+ });
640
+ } else {
641
+ await runViteBuildInSubprocess(
642
+ {
643
+ ...workerBaseOptions,
644
+ mode: 'client',
645
+ workbenchEnabled: workbenchConfig.enabled,
646
+ workbenchRoute: workbenchConfig.route,
647
+ analyticsEnabled,
648
+ },
649
+ logger
650
+ );
651
+ }
403
652
  result.client.included = true;
404
653
  result.client.duration = Date.now() - started;
405
654
  endClientDiagnostic?.();
@@ -411,15 +660,22 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
411
660
  if (config?.render === 'static' && hasWebFrontend) {
412
661
  logger.debug('Running static rendering (pre-rendering all routes)...');
413
662
  const endStaticDiagnostic = collector?.startDiagnostic('static-render');
414
- const { runStaticRender } = await import('./static-renderer');
415
- const staticResult = await runStaticRender({
416
- rootDir,
417
- logger,
418
- userPlugins: config?.plugins || [],
419
- });
420
- result.static.included = true;
421
- result.static.duration = staticResult.duration;
422
- result.static.routes = staticResult.routes;
663
+ if (dev) {
664
+ const { runStaticRender } = await import('./static-renderer');
665
+ const staticResult = await runStaticRender({
666
+ rootDir,
667
+ logger,
668
+ userPlugins: config?.plugins || [],
669
+ });
670
+ result.static.included = true;
671
+ result.static.duration = staticResult.duration;
672
+ result.static.routes = staticResult.routes;
673
+ } else {
674
+ const staticResult = await runStaticRenderInSubprocess(rootDir, logger);
675
+ result.static.included = true;
676
+ result.static.duration = staticResult.duration;
677
+ result.static.routes = staticResult.routes;
678
+ }
423
679
  endStaticDiagnostic?.();
424
680
  }
425
681
 
@@ -428,12 +684,24 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
428
684
  logger.debug('Building workbench assets...');
429
685
  const endWorkbenchDiagnostic = collector?.startDiagnostic('workbench-build');
430
686
  const started = Date.now();
431
- await runViteBuild({
432
- ...options,
433
- mode: 'workbench',
434
- workbenchRoute: workbenchConfig.route,
435
- workbenchEnabled: true,
436
- });
687
+ if (dev) {
688
+ await runViteBuild({
689
+ ...options,
690
+ mode: 'workbench',
691
+ workbenchRoute: workbenchConfig.route,
692
+ workbenchEnabled: true,
693
+ });
694
+ } else {
695
+ await runViteBuildInSubprocess(
696
+ {
697
+ ...workerBaseOptions,
698
+ mode: 'workbench',
699
+ workbenchRoute: workbenchConfig.route,
700
+ workbenchEnabled: true,
701
+ },
702
+ logger
703
+ );
704
+ }
437
705
  result.workbench.included = true;
438
706
  result.workbench.duration = Date.now() - started;
439
707
  endWorkbenchDiagnostic?.();
@@ -443,7 +711,11 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
443
711
  logger.debug('Building server...');
444
712
  const endServerDiagnostic = collector?.startDiagnostic('server-build');
445
713
  const serverStarted = Date.now();
446
- await runViteBuild({ ...options, mode: 'server' });
714
+ if (dev) {
715
+ await runViteBuild({ ...options, mode: 'server' });
716
+ } else {
717
+ await runViteBuildInSubprocess({ ...workerBaseOptions, mode: 'server' }, logger);
718
+ }
447
719
  result.server.included = true;
448
720
  result.server.duration = Date.now() - serverStarted;
449
721
  endServerDiagnostic?.();