@multiplayer-app/agent 0.1.20 → 0.1.22

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 (298) hide show
  1. package/README.md +14 -7
  2. package/dist/agent/definition.test.js +8 -3
  3. package/dist/agent/definition.test.js.map +1 -1
  4. package/dist/agent/profiles/coding.d.ts +1 -1
  5. package/dist/agent/profiles/coding.d.ts.map +1 -1
  6. package/dist/agent/profiles/coding.js +21 -4
  7. package/dist/agent/profiles/coding.js.map +1 -1
  8. package/dist/agent/profiles/index.js +1 -1
  9. package/dist/agent/profiles/index.js.map +1 -1
  10. package/dist/agent/profiles/research.d.ts.map +1 -1
  11. package/dist/agent/profiles/research.js +2 -2
  12. package/dist/agent/profiles/research.js.map +1 -1
  13. package/dist/agent/turn-history.d.ts.map +1 -1
  14. package/dist/agent/turn-history.js +7 -1
  15. package/dist/agent/turn-history.js.map +1 -1
  16. package/dist/agent/turn-runner.d.ts +8 -0
  17. package/dist/agent/turn-runner.d.ts.map +1 -1
  18. package/dist/agent/turn-runner.js +35 -10
  19. package/dist/agent/turn-runner.js.map +1 -1
  20. package/dist/app/app-host.d.ts +57 -0
  21. package/dist/app/app-host.d.ts.map +1 -0
  22. package/dist/app/app-host.js +252 -0
  23. package/dist/app/app-host.js.map +1 -0
  24. package/dist/app/app-host.test.d.ts +2 -0
  25. package/dist/app/app-host.test.d.ts.map +1 -0
  26. package/dist/app/app-host.test.js +639 -0
  27. package/dist/app/app-host.test.js.map +1 -0
  28. package/dist/app/build-log.d.ts +14 -0
  29. package/dist/app/build-log.d.ts.map +1 -0
  30. package/dist/app/build-log.js +173 -0
  31. package/dist/app/build-log.js.map +1 -0
  32. package/dist/app/build-log.test.d.ts +2 -0
  33. package/dist/app/build-log.test.d.ts.map +1 -0
  34. package/dist/app/build-log.test.js +255 -0
  35. package/dist/app/build-log.test.js.map +1 -0
  36. package/dist/app/config.d.ts +196 -0
  37. package/dist/app/config.d.ts.map +1 -0
  38. package/dist/app/config.js +362 -0
  39. package/dist/app/config.js.map +1 -0
  40. package/dist/app/config.test.d.ts +2 -0
  41. package/dist/app/config.test.d.ts.map +1 -0
  42. package/dist/app/config.test.js +224 -0
  43. package/dist/app/config.test.js.map +1 -0
  44. package/dist/app/detect.d.ts +68 -0
  45. package/dist/app/detect.d.ts.map +1 -0
  46. package/dist/app/detect.js +449 -0
  47. package/dist/app/detect.js.map +1 -0
  48. package/dist/app/detect.test.d.ts +2 -0
  49. package/dist/app/detect.test.d.ts.map +1 -0
  50. package/dist/app/detect.test.js +267 -0
  51. package/dist/app/detect.test.js.map +1 -0
  52. package/dist/app/env.d.ts +5 -0
  53. package/dist/app/env.d.ts.map +1 -0
  54. package/dist/app/env.js +22 -0
  55. package/dist/app/env.js.map +1 -0
  56. package/dist/app/functions-cli.d.ts +3 -0
  57. package/dist/app/functions-cli.d.ts.map +1 -0
  58. package/dist/app/functions-cli.js +27 -0
  59. package/dist/app/functions-cli.js.map +1 -0
  60. package/dist/app/functions-host.d.ts +15 -0
  61. package/dist/app/functions-host.d.ts.map +1 -0
  62. package/dist/app/functions-host.js +276 -0
  63. package/dist/app/functions-host.js.map +1 -0
  64. package/dist/app/functions-host.test.d.ts +2 -0
  65. package/dist/app/functions-host.test.d.ts.map +1 -0
  66. package/dist/app/functions-host.test.js +117 -0
  67. package/dist/app/functions-host.test.js.map +1 -0
  68. package/dist/app/functions-routes.d.ts +23 -0
  69. package/dist/app/functions-routes.d.ts.map +1 -0
  70. package/dist/app/functions-routes.js +85 -0
  71. package/dist/app/functions-routes.js.map +1 -0
  72. package/dist/app/functions-routes.test.d.ts +2 -0
  73. package/dist/app/functions-routes.test.d.ts.map +1 -0
  74. package/dist/app/functions-routes.test.js +40 -0
  75. package/dist/app/functions-routes.test.js.map +1 -0
  76. package/dist/app/manifest.d.ts +17 -0
  77. package/dist/app/manifest.d.ts.map +1 -0
  78. package/dist/app/manifest.js +64 -0
  79. package/dist/app/manifest.js.map +1 -0
  80. package/dist/app/manifest.test.d.ts +2 -0
  81. package/dist/app/manifest.test.d.ts.map +1 -0
  82. package/dist/app/manifest.test.js +173 -0
  83. package/dist/app/manifest.test.js.map +1 -0
  84. package/dist/app/release-artifacts.d.ts +43 -0
  85. package/dist/app/release-artifacts.d.ts.map +1 -0
  86. package/dist/app/release-artifacts.js +239 -0
  87. package/dist/app/release-artifacts.js.map +1 -0
  88. package/dist/app/release-artifacts.test.d.ts +2 -0
  89. package/dist/app/release-artifacts.test.d.ts.map +1 -0
  90. package/dist/app/release-artifacts.test.js +326 -0
  91. package/dist/app/release-artifacts.test.js.map +1 -0
  92. package/dist/app/route-pattern.d.ts +23 -0
  93. package/dist/app/route-pattern.d.ts.map +1 -0
  94. package/dist/app/route-pattern.js +339 -0
  95. package/dist/app/route-pattern.js.map +1 -0
  96. package/dist/app/route-pattern.test.d.ts +2 -0
  97. package/dist/app/route-pattern.test.d.ts.map +1 -0
  98. package/dist/app/route-pattern.test.js +187 -0
  99. package/dist/app/route-pattern.test.js.map +1 -0
  100. package/dist/app/rules.d.ts +61 -0
  101. package/dist/app/rules.d.ts.map +1 -0
  102. package/dist/app/rules.js +90 -0
  103. package/dist/app/rules.js.map +1 -0
  104. package/dist/app/rules.test.d.ts +2 -0
  105. package/dist/app/rules.test.d.ts.map +1 -0
  106. package/dist/app/rules.test.js +69 -0
  107. package/dist/app/rules.test.js.map +1 -0
  108. package/dist/app/runner.d.ts +109 -0
  109. package/dist/app/runner.d.ts.map +1 -0
  110. package/dist/app/runner.js +429 -0
  111. package/dist/app/runner.js.map +1 -0
  112. package/dist/app/site.d.ts +73 -0
  113. package/dist/app/site.d.ts.map +1 -0
  114. package/dist/app/site.js +161 -0
  115. package/dist/app/site.js.map +1 -0
  116. package/dist/app/site.test.d.ts +2 -0
  117. package/dist/app/site.test.d.ts.map +1 -0
  118. package/dist/app/site.test.js +291 -0
  119. package/dist/app/site.test.js.map +1 -0
  120. package/dist/app/static-server.d.ts +20 -0
  121. package/dist/app/static-server.d.ts.map +1 -0
  122. package/dist/app/static-server.js +145 -0
  123. package/dist/app/static-server.js.map +1 -0
  124. package/dist/persistence/conversation-store.d.ts +21 -0
  125. package/dist/persistence/conversation-store.d.ts.map +1 -0
  126. package/dist/persistence/conversation-store.js +59 -0
  127. package/dist/persistence/conversation-store.js.map +1 -0
  128. package/dist/persistence/conversation-store.test.d.ts +2 -0
  129. package/dist/persistence/conversation-store.test.d.ts.map +1 -0
  130. package/dist/persistence/conversation-store.test.js +98 -0
  131. package/dist/persistence/conversation-store.test.js.map +1 -0
  132. package/dist/persistence/git-sync.d.ts +14 -1
  133. package/dist/persistence/git-sync.d.ts.map +1 -1
  134. package/dist/persistence/git-sync.js +73 -17
  135. package/dist/persistence/git-sync.js.map +1 -1
  136. package/dist/persistence/git-sync.test.js +155 -6
  137. package/dist/persistence/git-sync.test.js.map +1 -1
  138. package/dist/persistence/snapshot-sync.d.ts +19 -2
  139. package/dist/persistence/snapshot-sync.d.ts.map +1 -1
  140. package/dist/persistence/snapshot-sync.js +194 -83
  141. package/dist/persistence/snapshot-sync.js.map +1 -1
  142. package/dist/persistence/snapshot-sync.test.js +145 -27
  143. package/dist/persistence/snapshot-sync.test.js.map +1 -1
  144. package/dist/persistence/workspace.d.ts +3 -0
  145. package/dist/persistence/workspace.d.ts.map +1 -1
  146. package/dist/persistence/workspace.js +1 -0
  147. package/dist/persistence/workspace.js.map +1 -1
  148. package/dist/research/extract-texts.d.ts.map +1 -1
  149. package/dist/research/extract-texts.js +6 -13
  150. package/dist/research/extract-texts.js.map +1 -1
  151. package/dist/research/filingramp/attachment-tools.d.ts.map +1 -1
  152. package/dist/research/filingramp/attachment-tools.js +7 -4
  153. package/dist/research/filingramp/attachment-tools.js.map +1 -1
  154. package/dist/research/filingramp/shared.d.ts.map +1 -1
  155. package/dist/research/filingramp/shared.js +5 -5
  156. package/dist/research/filingramp/shared.js.map +1 -1
  157. package/dist/runtime/app-mode.d.ts +10 -0
  158. package/dist/runtime/app-mode.d.ts.map +1 -0
  159. package/dist/runtime/app-mode.js +172 -0
  160. package/dist/runtime/app-mode.js.map +1 -0
  161. package/dist/runtime/app-mode.test.d.ts +2 -0
  162. package/dist/runtime/app-mode.test.d.ts.map +1 -0
  163. package/dist/runtime/app-mode.test.js +130 -0
  164. package/dist/runtime/app-mode.test.js.map +1 -0
  165. package/dist/runtime/app-rehearsal.d.ts +2 -0
  166. package/dist/runtime/app-rehearsal.d.ts.map +1 -0
  167. package/dist/runtime/app-rehearsal.js +56 -0
  168. package/dist/runtime/app-rehearsal.js.map +1 -0
  169. package/dist/runtime/build-mode.d.ts +18 -0
  170. package/dist/runtime/build-mode.d.ts.map +1 -0
  171. package/dist/runtime/build-mode.js +108 -0
  172. package/dist/runtime/build-mode.js.map +1 -0
  173. package/dist/runtime/build-mode.test.d.ts +2 -0
  174. package/dist/runtime/build-mode.test.d.ts.map +1 -0
  175. package/dist/runtime/build-mode.test.js +159 -0
  176. package/dist/runtime/build-mode.test.js.map +1 -0
  177. package/dist/runtime/cli.js +7 -2
  178. package/dist/runtime/cli.js.map +1 -1
  179. package/dist/runtime/config.d.ts +4 -0
  180. package/dist/runtime/config.d.ts.map +1 -1
  181. package/dist/runtime/config.js +13 -2
  182. package/dist/runtime/config.js.map +1 -1
  183. package/dist/runtime/guest-server.d.ts +3 -0
  184. package/dist/runtime/guest-server.d.ts.map +1 -0
  185. package/dist/runtime/{lambda-microvm.js → guest-server.js} +49 -18
  186. package/dist/runtime/guest-server.js.map +1 -0
  187. package/dist/runtime/image-hooks.d.ts +5 -0
  188. package/dist/runtime/image-hooks.d.ts.map +1 -0
  189. package/dist/runtime/image-hooks.js +68 -0
  190. package/dist/runtime/image-hooks.js.map +1 -0
  191. package/dist/runtime/image-hooks.test.d.ts +2 -0
  192. package/dist/runtime/image-hooks.test.d.ts.map +1 -0
  193. package/dist/runtime/image-hooks.test.js +74 -0
  194. package/dist/runtime/image-hooks.test.js.map +1 -0
  195. package/dist/runtime/index.d.ts +8 -3
  196. package/dist/runtime/index.d.ts.map +1 -1
  197. package/dist/runtime/index.js +53 -35
  198. package/dist/runtime/index.js.map +1 -1
  199. package/dist/runtime/listening-ports.d.ts +13 -0
  200. package/dist/runtime/listening-ports.d.ts.map +1 -0
  201. package/dist/runtime/listening-ports.js +78 -0
  202. package/dist/runtime/listening-ports.js.map +1 -0
  203. package/dist/runtime/listening-ports.test.d.ts +2 -0
  204. package/dist/runtime/listening-ports.test.d.ts.map +1 -0
  205. package/dist/runtime/listening-ports.test.js +39 -0
  206. package/dist/runtime/listening-ports.test.js.map +1 -0
  207. package/dist/runtime/preview-proxy.d.ts +56 -0
  208. package/dist/runtime/preview-proxy.d.ts.map +1 -0
  209. package/dist/runtime/preview-proxy.js +302 -0
  210. package/dist/runtime/preview-proxy.js.map +1 -0
  211. package/dist/runtime/preview-proxy.test.d.ts +2 -0
  212. package/dist/runtime/preview-proxy.test.d.ts.map +1 -0
  213. package/dist/runtime/preview-proxy.test.js +122 -0
  214. package/dist/runtime/preview-proxy.test.js.map +1 -0
  215. package/dist/runtime/session-mode.d.ts +3 -0
  216. package/dist/runtime/session-mode.d.ts.map +1 -0
  217. package/dist/runtime/session-mode.js +60 -0
  218. package/dist/runtime/session-mode.js.map +1 -0
  219. package/dist/runtime/workload.d.ts +44 -0
  220. package/dist/runtime/workload.d.ts.map +1 -0
  221. package/dist/runtime/workload.js +222 -0
  222. package/dist/runtime/workload.js.map +1 -0
  223. package/dist/runtime/workload.test.d.ts +2 -0
  224. package/dist/runtime/workload.test.d.ts.map +1 -0
  225. package/dist/runtime/workload.test.js +235 -0
  226. package/dist/runtime/workload.test.js.map +1 -0
  227. package/dist/session/conversation.d.ts +3 -2
  228. package/dist/session/conversation.d.ts.map +1 -1
  229. package/dist/session/conversation.js +11 -7
  230. package/dist/session/conversation.js.map +1 -1
  231. package/dist/session/conversation.test.js +2 -3
  232. package/dist/session/conversation.test.js.map +1 -1
  233. package/dist/session/runtime.d.ts +16 -3
  234. package/dist/session/runtime.d.ts.map +1 -1
  235. package/dist/session/runtime.js +80 -13
  236. package/dist/session/runtime.js.map +1 -1
  237. package/dist/session/runtime.test.js +18 -7
  238. package/dist/session/runtime.test.js.map +1 -1
  239. package/dist/session/tools.d.ts.map +1 -1
  240. package/dist/session/tools.js +3 -3
  241. package/dist/session/tools.js.map +1 -1
  242. package/dist/task/loop.d.ts.map +1 -1
  243. package/dist/task/loop.js +6 -4
  244. package/dist/task/loop.js.map +1 -1
  245. package/dist/tools/exec-tools.d.ts +12 -0
  246. package/dist/tools/exec-tools.d.ts.map +1 -1
  247. package/dist/tools/exec-tools.js +46 -3
  248. package/dist/tools/exec-tools.js.map +1 -1
  249. package/dist/tools/exec-tools.test.d.ts +2 -0
  250. package/dist/tools/exec-tools.test.d.ts.map +1 -0
  251. package/dist/tools/exec-tools.test.js +48 -0
  252. package/dist/tools/exec-tools.test.js.map +1 -0
  253. package/dist/tools/fs-tools.d.ts.map +1 -1
  254. package/dist/tools/fs-tools.js +5 -5
  255. package/dist/tools/fs-tools.js.map +1 -1
  256. package/dist/transport/link.d.ts.map +1 -1
  257. package/dist/transport/link.js +16 -5
  258. package/dist/transport/link.js.map +1 -1
  259. package/dist/transport/link.test.js +24 -0
  260. package/dist/transport/link.test.js.map +1 -1
  261. package/dist/transport/protocol.d.ts +215 -3
  262. package/dist/transport/protocol.d.ts.map +1 -1
  263. package/dist/transport/protocol.js +163 -7
  264. package/dist/transport/protocol.js.map +1 -1
  265. package/dist/transport/protocol.test.js +41 -0
  266. package/dist/transport/protocol.test.js.map +1 -1
  267. package/dist/workspace-sync/eligibility.d.ts +14 -0
  268. package/dist/workspace-sync/eligibility.d.ts.map +1 -0
  269. package/dist/workspace-sync/eligibility.js +53 -0
  270. package/dist/workspace-sync/eligibility.js.map +1 -0
  271. package/dist/workspace-sync/merge.d.ts +15 -0
  272. package/dist/workspace-sync/merge.d.ts.map +1 -0
  273. package/dist/workspace-sync/merge.js +66 -0
  274. package/dist/workspace-sync/merge.js.map +1 -0
  275. package/dist/workspace-sync/merge.test.d.ts +2 -0
  276. package/dist/workspace-sync/merge.test.d.ts.map +1 -0
  277. package/dist/workspace-sync/merge.test.js +28 -0
  278. package/dist/workspace-sync/merge.test.js.map +1 -0
  279. package/dist/workspace-sync/workspace-sync.d.ts +48 -0
  280. package/dist/workspace-sync/workspace-sync.d.ts.map +1 -0
  281. package/dist/workspace-sync/workspace-sync.js +308 -0
  282. package/dist/workspace-sync/workspace-sync.js.map +1 -0
  283. package/dist/workspace-sync/workspace-sync.test.d.ts +2 -0
  284. package/dist/workspace-sync/workspace-sync.test.d.ts.map +1 -0
  285. package/dist/workspace-sync/workspace-sync.test.js +111 -0
  286. package/dist/workspace-sync/workspace-sync.test.js.map +1 -0
  287. package/dist/workspace-sync/write-drafts.d.ts +24 -0
  288. package/dist/workspace-sync/write-drafts.d.ts.map +1 -0
  289. package/dist/workspace-sync/write-drafts.js +87 -0
  290. package/dist/workspace-sync/write-drafts.js.map +1 -0
  291. package/dist/workspace-sync/write-drafts.test.d.ts +2 -0
  292. package/dist/workspace-sync/write-drafts.test.d.ts.map +1 -0
  293. package/dist/workspace-sync/write-drafts.test.js +36 -0
  294. package/dist/workspace-sync/write-drafts.test.js.map +1 -0
  295. package/package.json +12 -2
  296. package/dist/runtime/lambda-microvm.d.ts +0 -3
  297. package/dist/runtime/lambda-microvm.d.ts.map +0 -1
  298. package/dist/runtime/lambda-microvm.js.map +0 -1
@@ -0,0 +1,639 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_fs_1 = require("node:fs");
8
+ const node_http_1 = __importDefault(require("node:http"));
9
+ const node_net_1 = __importDefault(require("node:net"));
10
+ const node_os_1 = __importDefault(require("node:os"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const node_test_1 = require("node:test");
13
+ const protocol_1 = require("../transport/protocol");
14
+ const preview_proxy_1 = require("../runtime/preview-proxy");
15
+ const app_host_1 = require("./app-host");
16
+ const runner_1 = require("./runner");
17
+ const env_1 = require("./env");
18
+ const FAST = { backoff: { initialMs: 20, maxMs: 50, resetAfterMs: 60000 }, readyPollMs: 50 };
19
+ function freePort() {
20
+ return new Promise((resolve) => {
21
+ const server = node_net_1.default.createServer();
22
+ server.listen(0, '127.0.0.1', () => {
23
+ const { port } = server.address();
24
+ server.close(() => resolve(port));
25
+ });
26
+ });
27
+ }
28
+ async function writeFiles(dir, files) {
29
+ for (const [name, content] of Object.entries(files)) {
30
+ await node_fs_1.promises.mkdir(node_path_1.default.dirname(node_path_1.default.join(dir, name)), { recursive: true });
31
+ await node_fs_1.promises.writeFile(node_path_1.default.join(dir, name), content);
32
+ }
33
+ }
34
+ function request(port, url, headers = {}) {
35
+ return new Promise((resolve, reject) => {
36
+ node_http_1.default.get({ host: '127.0.0.1', port, path: url, headers: { [protocol_1.PREVIEW_PORT_HEADER]: 'app', ...headers } }, (res) => {
37
+ let body = '';
38
+ res.on('data', (chunk) => (body += chunk));
39
+ res.on('end', () => { var _a; return resolve({ status: (_a = res.statusCode) !== null && _a !== void 0 ? _a : 0, headers: res.headers, body }); });
40
+ }).on('error', reject);
41
+ });
42
+ }
43
+ const API_SERVER = `require('http').createServer((req, res) => {
44
+ res.setHeader('Content-Type', 'application/json')
45
+ res.end(JSON.stringify({ url: req.url, port: process.env.PORT, secret: process.env.OPENROUTER_API_KEY ?? null }))
46
+ }).listen(Number(process.env.PORT), '127.0.0.1')`;
47
+ (0, node_test_1.describe)('app host (session VM, dev mode)', () => {
48
+ let dir;
49
+ let apiPort;
50
+ let host;
51
+ let proxy;
52
+ let proxyPort;
53
+ (0, node_test_1.before)(async () => {
54
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
55
+ apiPort = await freePort();
56
+ await writeFiles(dir, {
57
+ 'server.js': API_SERVER,
58
+ 'public/index.html': '<h1>web</h1>',
59
+ 'public/assets/app.js': 'console.log(1)',
60
+ '.builder/builder.json': JSON.stringify({
61
+ services: {
62
+ // No devCommand: served from its output directory by the guest.
63
+ web: {
64
+ outputDirectory: 'public',
65
+ rewrites: [{ source: '/((?!assets/).*)', destination: '/index.html' }],
66
+ headers: [{ source: '/assets/(.*)', headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }] }],
67
+ },
68
+ api: { devCommand: [process.execPath, 'server.js'], port: apiPort },
69
+ },
70
+ rewrites: [
71
+ { source: '/api/(.*)', destination: { service: 'api' } },
72
+ { source: '/(.*)', destination: { service: 'web' } },
73
+ ],
74
+ daemons: { worker: { command: [process.execPath, '-e', 'process.exit(3)'] } },
75
+ }),
76
+ });
77
+ host = new app_host_1.AppHost({ dir, mode: 'dev', env: (0, env_1.sessionAppEnv)({ ...process.env, OPENROUTER_API_KEY: 'model-key' }), ...FAST });
78
+ (0, preview_proxy_1.setAppHandler)(host);
79
+ proxy = (0, preview_proxy_1.startPreviewServer)(0);
80
+ await new Promise((resolve) => proxy.once('listening', resolve));
81
+ proxyPort = proxy.address().port;
82
+ await host.start();
83
+ });
84
+ (0, node_test_1.after)(async () => {
85
+ await host.stop();
86
+ proxy.closeAllConnections();
87
+ proxy.close();
88
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
89
+ });
90
+ (0, node_test_1.it)('runs every service and reports them', async () => {
91
+ const status = await host.settled(10000);
92
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running');
93
+ const api = status === null || status === void 0 ? void 0 : status.processes.find((process) => process.name === 'api');
94
+ strict_1.default.deepEqual([api === null || api === void 0 ? void 0 : api.state, api === null || api === void 0 ? void 0 : api.port, api === null || api === void 0 ? void 0 : api.log], ['running', apiPort, '.builder/logs/api.log']);
95
+ });
96
+ (0, node_test_1.it)('routes the session URL by the rewrites', async () => {
97
+ const api = await request(proxyPort, '/api/hello?x=1');
98
+ strict_1.default.deepEqual(JSON.parse(api.body), { url: '/api/hello?x=1', port: String(apiPort), secret: null });
99
+ const page = await request(proxyPort, '/dashboard/settings');
100
+ strict_1.default.deepEqual([page.status, page.body], [200, '<h1>web</h1>']);
101
+ const asset = await request(proxyPort, '/assets/app.js');
102
+ strict_1.default.equal(asset.headers['cache-control'], 'public, max-age=31536000, immutable');
103
+ strict_1.default.equal((await request(proxyPort, '/assets/missing.js')).status, 404);
104
+ });
105
+ (0, node_test_1.it)('never serves dotfiles from a static service', async () => {
106
+ await writeFiles(dir, { 'public/.env': 'SECRET=1' });
107
+ strict_1.default.equal((await request(proxyPort, '/.env')).body, '<h1>web</h1>');
108
+ });
109
+ (0, node_test_1.it)('restarts a process that exits, and says how it exited', async () => {
110
+ var _a, _b;
111
+ await new Promise((resolve) => setTimeout(resolve, 300));
112
+ const worker = (_a = host.status()) === null || _a === void 0 ? void 0 : _a.processes.find((process) => process.name === 'worker');
113
+ strict_1.default.equal(worker === null || worker === void 0 ? void 0 : worker.kind, 'daemon');
114
+ strict_1.default.equal(worker === null || worker === void 0 ? void 0 : worker.exitCode, 3);
115
+ strict_1.default.ok(((_b = worker === null || worker === void 0 ? void 0 : worker.restarts) !== null && _b !== void 0 ? _b : 0) >= 1);
116
+ });
117
+ });
118
+ (0, node_test_1.describe)('app host failures', () => {
119
+ let dir;
120
+ (0, node_test_1.before)(async () => {
121
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
122
+ });
123
+ (0, node_test_1.after)(() => node_fs_1.promises.rm(dir, { recursive: true, force: true }));
124
+ (0, node_test_1.it)('serves why an invalid builder.json cannot run', async () => {
125
+ var _a;
126
+ await writeFiles(dir, { '.builder/builder.json': JSON.stringify({ services: { a: { devCommand: 'x' }, b: { devCommand: 'y' } } }) });
127
+ const host = new app_host_1.AppHost({ dir, mode: 'dev', env: {}, ...FAST });
128
+ const status = await host.start();
129
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'failed');
130
+ strict_1.default.match((_a = status === null || status === void 0 ? void 0 : status.error) !== null && _a !== void 0 ? _a : '', /rewrites: are needed/);
131
+ });
132
+ (0, node_test_1.it)('stops at a failed install and points at its log', async () => {
133
+ await writeFiles(dir, { '.builder/builder.json': JSON.stringify({ services: { web: { installCommand: 'echo no network; exit 4', devCommand: 'true' } } }) });
134
+ const host = new app_host_1.AppHost({ dir, mode: 'dev', env: { PATH: process.env.PATH }, ...FAST });
135
+ const status = await host.start();
136
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'failed');
137
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.error, 'Install failed in . (exit 4); see .builder/logs/install.log');
138
+ strict_1.default.match(await node_fs_1.promises.readFile(node_path_1.default.join(dir, '.builder/logs/install.log'), 'utf8'), /no network/);
139
+ });
140
+ (0, node_test_1.it)('has no app without a builder.json', async () => {
141
+ await node_fs_1.promises.rm(node_path_1.default.join(dir, '.builder/builder.json'));
142
+ strict_1.default.equal(await new app_host_1.AppHost({ dir, mode: 'dev', env: {} }).start(), null);
143
+ });
144
+ });
145
+ (0, node_test_1.describe)('app host (session VM, no builder.json)', () => {
146
+ let dir;
147
+ let host;
148
+ let proxy;
149
+ let proxyPort;
150
+ (0, node_test_1.before)(async () => {
151
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
152
+ host = new app_host_1.AppHost({ dir, mode: 'dev', env: { PATH: process.env.PATH }, ...FAST });
153
+ (0, preview_proxy_1.setAppHandler)(host);
154
+ proxy = (0, preview_proxy_1.startPreviewServer)(0);
155
+ await new Promise((resolve) => proxy.once('listening', resolve));
156
+ proxyPort = proxy.address().port;
157
+ });
158
+ (0, node_test_1.after)(async () => {
159
+ await host.stop();
160
+ proxy.closeAllConnections();
161
+ proxy.close();
162
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
163
+ });
164
+ (0, node_test_1.it)('serves static files it detects, and writes their builder.json', async () => {
165
+ await writeFiles(dir, { 'public/index.html': '<h1>dashboard</h1>', 'public/package.json': 'on purpose', 'build-data.js': '' });
166
+ const status = await host.start();
167
+ strict_1.default.deepEqual([status === null || status === void 0 ? void 0 : status.phase, status === null || status === void 0 ? void 0 : status.framework, status === null || status === void 0 ? void 0 : status.source], ['running', null, 'detected']);
168
+ strict_1.default.equal((await request(proxyPort, '/')).body, '<h1>dashboard</h1>');
169
+ // An output folder holds what is meant to be served.
170
+ strict_1.default.equal((await request(proxyPort, '/package.json')).body, 'on purpose');
171
+ const { $generated, ...written } = JSON.parse(await node_fs_1.promises.readFile(node_path_1.default.join(dir, '.builder/builder.json'), 'utf8'));
172
+ strict_1.default.equal(typeof $generated, 'string');
173
+ strict_1.default.deepEqual(written, {
174
+ services: { web: { root: '.', framework: null, outputDirectory: 'public' } },
175
+ rewrites: [{ source: '/(.*)', destination: { service: 'web' } }],
176
+ });
177
+ });
178
+ (0, node_test_1.it)('after a turn, starts the server the workspace now holds', async () => {
179
+ var _a, _b;
180
+ const port = await freePort();
181
+ await writeFiles(dir, {
182
+ 'package.json': JSON.stringify({ scripts: { start: `node server.js` } }),
183
+ 'server.js': API_SERVER.replace('Number(process.env.PORT)', String(port)),
184
+ });
185
+ // A start script: the package's own ("Other"), so the guest gives it $PORT; this server keeps its own.
186
+ await writeFiles(dir, { '.builder/builder.json': JSON.stringify({ services: { app: { port, installCommand: 'true', devCommand: [process.execPath, 'server.js'] } } }, null, 2) });
187
+ await host.refresh();
188
+ const status = await host.settled(10000);
189
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running');
190
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.source, 'builder.json');
191
+ strict_1.default.equal(JSON.parse((await request(proxyPort, '/hello')).body).url, '/hello');
192
+ // Unchanged since: the running app is left alone.
193
+ const before = (_a = host.status()) === null || _a === void 0 ? void 0 : _a.processes[0];
194
+ await host.refresh();
195
+ strict_1.default.equal((_b = host.status()) === null || _b === void 0 ? void 0 : _b.processes[0].restarts, before === null || before === void 0 ? void 0 : before.restarts);
196
+ });
197
+ });
198
+ (0, node_test_1.describe)('app host (services in packages/, no builder.json)', () => {
199
+ let dir;
200
+ let host;
201
+ let proxy;
202
+ let proxyPort;
203
+ (0, node_test_1.before)(async () => {
204
+ var _a;
205
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
206
+ await writeFiles(dir, {
207
+ 'README.md': 'not part of any service',
208
+ 'packages/web/index.html': '<h1>web</h1>',
209
+ 'packages/web/styles.css': 'web styles',
210
+ 'packages/web/package.json': JSON.stringify({ name: 'web', private: true }),
211
+ 'packages/web/package-lock.json': JSON.stringify({ name: 'web', lockfileVersion: 3, requires: true, packages: {} }),
212
+ 'packages/web/docs/package.json': 'the site\'s own file',
213
+ 'packages/admin/index.html': '<h1>admin</h1>',
214
+ 'packages/admin/styles.css': 'admin styles',
215
+ 'packages/api/package.json': JSON.stringify({ scripts: { start: 'node server.js' } }),
216
+ 'packages/api/server.js': API_SERVER,
217
+ });
218
+ host = new app_host_1.AppHost({ dir, mode: 'dev', env: { PATH: process.env.PATH }, ...FAST });
219
+ (0, preview_proxy_1.setAppHandler)(host);
220
+ proxy = (0, preview_proxy_1.startPreviewServer)(0);
221
+ await new Promise((resolve) => proxy.once('listening', resolve));
222
+ proxyPort = proxy.address().port;
223
+ await host.start();
224
+ strict_1.default.equal((_a = (await host.settled(30000))) === null || _a === void 0 ? void 0 : _a.phase, 'running');
225
+ });
226
+ (0, node_test_1.after)(async () => {
227
+ await host.stop();
228
+ proxy.closeAllConnections();
229
+ proxy.close();
230
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
231
+ });
232
+ (0, node_test_1.it)('serves the main site at the root of the URL, and the server below /api', async () => {
233
+ strict_1.default.equal((await request(proxyPort, '/')).body, '<h1>web</h1>');
234
+ strict_1.default.equal((await request(proxyPort, '/styles.css')).body, 'web styles');
235
+ const api = JSON.parse((await request(proxyPort, '/api/filings')).body);
236
+ strict_1.default.equal(api.url, '/api/filings');
237
+ });
238
+ (0, node_test_1.it)('serves a second frontend below its name, with its own relative files', async () => {
239
+ const bare = await request(proxyPort, '/admin');
240
+ strict_1.default.deepEqual([bare.status, bare.headers.location], [307, '/admin/']);
241
+ strict_1.default.equal((await request(proxyPort, '/admin/')).body, '<h1>admin</h1>');
242
+ strict_1.default.equal((await request(proxyPort, '/admin/styles.css')).body, 'admin styles');
243
+ });
244
+ (0, node_test_1.it)('opens nothing around the services', async () => {
245
+ for (const url of ['/packages/api/server.js', '/README.md', '/server.js']) {
246
+ strict_1.default.equal((await request(proxyPort, url)).status, 404, url);
247
+ }
248
+ });
249
+ (0, node_test_1.it)('keeps a service\'s project files out of its site, and serves the site\'s own', async () => {
250
+ for (const url of ['/package.json', '/package-lock.json', '/PACKAGE.JSON', '/%2Fpackage.json', '/admin/package.json']) {
251
+ strict_1.default.equal((await request(proxyPort, url)).status, 404, url);
252
+ }
253
+ strict_1.default.equal((await request(proxyPort, '/docs/package.json')).body, 'the site\'s own file');
254
+ });
255
+ });
256
+ (0, node_test_1.describe)('app host (a site beside a server still starting)', () => {
257
+ const html = { accept: 'text/html,application/xhtml+xml,*/*;q=0.8' };
258
+ let dir;
259
+ let host;
260
+ let proxy;
261
+ let proxyPort;
262
+ const startWith = async (server) => {
263
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
264
+ await writeFiles(dir, {
265
+ 'packages/web/index.html': '<h1>web</h1>',
266
+ 'packages/web/app.js': 'the script',
267
+ 'packages/api/package.json': JSON.stringify({ scripts: { start: 'node server.js' } }),
268
+ 'packages/api/server.js': server,
269
+ });
270
+ host = new app_host_1.AppHost({ dir, mode: 'dev', env: { PATH: process.env.PATH }, ...FAST });
271
+ (0, preview_proxy_1.setAppHandler)(host);
272
+ proxy = (0, preview_proxy_1.startPreviewServer)(0);
273
+ await new Promise((resolve) => proxy.once('listening', resolve));
274
+ proxyPort = proxy.address().port;
275
+ await host.start();
276
+ };
277
+ (0, node_test_1.afterEach)(async () => {
278
+ await host.stop();
279
+ proxy.closeAllConnections();
280
+ proxy.close();
281
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
282
+ });
283
+ const until = async (url, headers, wanted) => {
284
+ const deadline = Date.now() + 15000;
285
+ for (;;) {
286
+ const answer = await request(proxyPort, url, headers);
287
+ if (answer.status === wanted || Date.now() > deadline)
288
+ return answer;
289
+ await new Promise((resolve) => setTimeout(resolve, 50));
290
+ }
291
+ };
292
+ (0, node_test_1.it)('holds a page load until the server is up, and never its assets', async () => {
293
+ await startWith(`setTimeout(() => require('http').createServer((req, res) => res.end('ok')).listen(Number(process.env.PORT), '127.0.0.1'), 2500)`);
294
+ const held = await until('/', html, 503);
295
+ strict_1.default.equal(held.status, 503);
296
+ strict_1.default.match(held.body, /http-equiv="refresh"/);
297
+ strict_1.default.equal((await request(proxyPort, '/app.js', { accept: '*/*' })).body, 'the script');
298
+ const page = await until('/', html, 200);
299
+ strict_1.default.equal(page.body, '<h1>web</h1>');
300
+ strict_1.default.equal((await request(proxyPort, '/api/x')).body, 'ok');
301
+ });
302
+ (0, node_test_1.it)('stops holding it once the server has crashed: the site doesn\'t wait for what may never start', async () => {
303
+ await startWith(`process.exit(1)`);
304
+ const page = await until('/', html, 200);
305
+ strict_1.default.equal(page.status, 200);
306
+ strict_1.default.equal(page.body, '<h1>web</h1>');
307
+ });
308
+ });
309
+ (0, node_test_1.describe)('app host (vercel.json\'s routing)', () => {
310
+ let dir;
311
+ let host;
312
+ let proxy;
313
+ let proxyPort;
314
+ const noRedirect = async (url) => request(proxyPort, url);
315
+ const start = async (files, env = { PATH: process.env.PATH }) => {
316
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
317
+ await node_fs_1.promises.mkdir(dir, { recursive: true });
318
+ await writeFiles(dir, files);
319
+ await (host === null || host === void 0 ? void 0 : host.stop());
320
+ host = new app_host_1.AppHost({ dir, mode: 'dev', env, ...FAST });
321
+ (0, preview_proxy_1.setAppHandler)(host);
322
+ await host.start();
323
+ return host.settled(15000);
324
+ };
325
+ (0, node_test_1.before)(async () => {
326
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
327
+ proxy = (0, preview_proxy_1.startPreviewServer)(0);
328
+ await new Promise((resolve) => proxy.once('listening', resolve));
329
+ proxyPort = proxy.address().port;
330
+ });
331
+ (0, node_test_1.after)(async () => {
332
+ await (host === null || host === void 0 ? void 0 : host.stop());
333
+ proxy.closeAllConnections();
334
+ proxy.close();
335
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
336
+ });
337
+ (0, node_test_1.it)('applies clean URLs and redirects to a static site', async () => {
338
+ await start({
339
+ 'about.html': 'about page',
340
+ 'index.html': 'home',
341
+ '.builder/builder.json': JSON.stringify({ services: { site: { outputDirectory: '.', cleanUrls: true, redirects: [{ source: '/old', destination: '/about', permanent: false }] } } }),
342
+ });
343
+ strict_1.default.equal((await noRedirect('/about')).body, 'about page');
344
+ const clean = await noRedirect('/about.html?a=1');
345
+ strict_1.default.deepEqual([clean.status, clean.headers.location], [308, '/about?a=1']);
346
+ const old = await noRedirect('/old');
347
+ strict_1.default.deepEqual([old.status, old.headers.location], [307, '/about']);
348
+ });
349
+ (0, node_test_1.it)('rewrites requests to a server and sets headers on its answers', async () => {
350
+ const port = await freePort();
351
+ const status = await start({
352
+ 'server.js': API_SERVER,
353
+ '.builder/builder.json': JSON.stringify({
354
+ services: {
355
+ app: {
356
+ devCommand: [process.execPath, 'server.js'],
357
+ port,
358
+ rewrites: [{ source: '/v1/(.*)', destination: '/api/$1' }],
359
+ headers: [{ source: '/(.*)', headers: [{ key: 'X-Frame-Options', value: 'DENY' }, { key: 'Content-Type', value: 'application/x-custom' }] }],
360
+ redirects: [{ source: '/gone', destination: '/v1/here' }],
361
+ trailingSlash: false,
362
+ },
363
+ },
364
+ }),
365
+ });
366
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running');
367
+ const rewritten = await noRedirect('/v1/users?a=1');
368
+ strict_1.default.equal(JSON.parse(rewritten.body).url, '/api/users?a=1');
369
+ strict_1.default.equal(rewritten.headers['x-frame-options'], 'DENY');
370
+ strict_1.default.equal(rewritten.headers['content-type'], 'application/x-custom', 'over the server\'s own');
371
+ strict_1.default.equal(JSON.parse((await noRedirect('/plain')).body).url, '/plain');
372
+ strict_1.default.deepEqual([(await noRedirect('/gone')).status, (await noRedirect('/gone')).headers.location], [308, '/v1/here']);
373
+ strict_1.default.equal((await noRedirect('/plain/')).headers.location, '/plain');
374
+ });
375
+ (0, node_test_1.it)('leaves a dev server standing in for a static site to route for itself', async () => {
376
+ const port = await freePort();
377
+ const status = await start({
378
+ 'server.js': API_SERVER,
379
+ '.builder/builder.json': JSON.stringify({
380
+ services: {
381
+ app: {
382
+ devCommand: [process.execPath, 'server.js'],
383
+ port,
384
+ outputDirectory: 'dist',
385
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
386
+ trailingSlash: true,
387
+ },
388
+ },
389
+ }),
390
+ });
391
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running');
392
+ const asset = await noRedirect('/@vite/client');
393
+ strict_1.default.equal(asset.status, 200, 'no trailing slash forced on it');
394
+ strict_1.default.equal(JSON.parse(asset.body).url, '/@vite/client', 'no SPA fallback in front of it');
395
+ });
396
+ (0, node_test_1.it)('passes what a static service has no file for to the next match: api + static files', async () => {
397
+ const port = await freePort();
398
+ const status = await start({
399
+ 'server.js': API_SERVER,
400
+ 'public/index.html': 'home',
401
+ 'public/logo.txt': 'logo',
402
+ '.builder/builder.json': JSON.stringify({
403
+ services: { web: { outputDirectory: 'public' }, api: { devCommand: [process.execPath, 'server.js'], port } },
404
+ rewrites: [
405
+ { source: '/(.*)', destination: { service: 'web' } },
406
+ { source: '/(.*)', destination: { service: 'api' } },
407
+ ],
408
+ }),
409
+ });
410
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running', JSON.stringify(status));
411
+ strict_1.default.equal((await noRedirect('/')).body, 'home');
412
+ strict_1.default.equal((await noRedirect('/logo.txt')).body, 'logo');
413
+ strict_1.default.equal(JSON.parse((await noRedirect('/users?a=1')).body).url, '/users?a=1');
414
+ // What static files can't answer goes on too, with its body.
415
+ const posted = await new Promise((resolve, reject) => {
416
+ const req = node_http_1.default.request({ host: '127.0.0.1', port: proxyPort, path: '/logo.txt', method: 'POST', headers: { [protocol_1.PREVIEW_PORT_HEADER]: 'app' } }, (res) => {
417
+ let body = '';
418
+ res.setEncoding('utf8');
419
+ res.on('data', (chunk) => (body += chunk));
420
+ res.on('end', () => resolve({ status: res.statusCode, body }));
421
+ });
422
+ req.on('error', reject);
423
+ req.end('payload');
424
+ });
425
+ strict_1.default.equal(posted.status, 200);
426
+ strict_1.default.equal(JSON.parse(posted.body).url, '/logo.txt');
427
+ });
428
+ (0, node_test_1.it)('answers 404 once a static service is the last to match, and for a path no rule matches', async () => {
429
+ await start({
430
+ 'site/index.html': 'home',
431
+ '.builder/builder.json': JSON.stringify({ services: { site: { outputDirectory: 'site' } }, rewrites: [{ source: '/docs/(.*)', destination: { service: 'site' } }] }),
432
+ });
433
+ strict_1.default.equal((await noRedirect('/docs/missing')).status, 404);
434
+ strict_1.default.equal((await noRedirect('/elsewhere')).status, 404);
435
+ });
436
+ const withFunctionsBin = async () => {
437
+ const bin = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-bin-'));
438
+ await node_fs_1.promises.symlink(node_path_1.default.join(__dirname, 'functions-cli.js'), node_path_1.default.join(bin, 'builder-functions'));
439
+ await node_fs_1.promises.chmod(node_path_1.default.join(__dirname, 'functions-cli.js'), 0o755);
440
+ return bin;
441
+ };
442
+ (0, node_test_1.it)('runs functions from the folder and URL builder.json names, and ignores an api/ folder it does not', async () => {
443
+ const bin = await withFunctionsBin();
444
+ const status = await start({
445
+ 'index.html': 'home',
446
+ 'api/ignored.js': `export default (req, res) => res.send('should not run')`,
447
+ 'backend/handlers/ping.js': `export default (req, res) => res.send('pong')`,
448
+ '.builder/builder.json': JSON.stringify({
449
+ services: { web: { outputDirectory: '.' }, handlers: { functions: { directory: 'backend/handlers', path: '/svc' } } },
450
+ rewrites: [
451
+ { source: '/svc/(.*)', destination: { service: 'handlers' } },
452
+ { source: '/(.*)', destination: { service: 'web' } },
453
+ ],
454
+ }),
455
+ }, { PATH: `${bin}${node_path_1.default.delimiter}${node_path_1.default.dirname(process.execPath)}${node_path_1.default.delimiter}${process.env.PATH}` });
456
+ try {
457
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running', JSON.stringify(status));
458
+ strict_1.default.equal((await noRedirect('/svc/ping')).body, 'pong');
459
+ strict_1.default.equal((await noRedirect('/')).body, 'home');
460
+ strict_1.default.equal((await noRedirect('/backend/handlers/ping.js')).status, 404, 'never the source');
461
+ strict_1.default.notEqual((await noRedirect('/api/ignored')).body, 'should not run');
462
+ }
463
+ finally {
464
+ await host.stop();
465
+ await node_fs_1.promises.rm(bin, { recursive: true, force: true });
466
+ }
467
+ });
468
+ (0, node_test_1.it)('runs functions from an api/ folder beside a static site, through the real command', async () => {
469
+ var _a;
470
+ const bin = await withFunctionsBin();
471
+ const status = await start({
472
+ 'index.html': 'home',
473
+ 'api/hello.js': `export default function handler(req, res) { res.json({ hello: req.query.name ?? 'world', port: process.env.PORT }) }`,
474
+ }, { PATH: `${bin}${node_path_1.default.delimiter}${node_path_1.default.dirname(process.execPath)}${node_path_1.default.delimiter}${process.env.PATH}` });
475
+ try {
476
+ strict_1.default.equal(status === null || status === void 0 ? void 0 : status.phase, 'running', JSON.stringify(status));
477
+ strict_1.default.equal((await noRedirect('/')).body, 'home');
478
+ strict_1.default.equal(JSON.parse((await noRedirect('/api/hello?name=ada')).body).hello, 'ada');
479
+ strict_1.default.equal((await noRedirect('/api/missing')).status, 404);
480
+ // Restarted on a change to a handler it has loaded (--watch).
481
+ await node_fs_1.promises.writeFile(node_path_1.default.join(dir, 'api/hello.js'), `export default function handler(req, res) { res.json({ hello: 'changed' }) }`);
482
+ let changed = '';
483
+ for (let attempt = 0; attempt < 50 && changed !== 'changed'; attempt++) {
484
+ await new Promise((resolve) => setTimeout(resolve, 100));
485
+ const answer = await noRedirect('/api/hello').catch(() => undefined);
486
+ if ((answer === null || answer === void 0 ? void 0 : answer.status) === 200)
487
+ changed = JSON.parse(answer.body).hello;
488
+ }
489
+ strict_1.default.equal(changed, 'changed');
490
+ }
491
+ finally {
492
+ await host.stop();
493
+ await node_fs_1.promises.rm(bin, { recursive: true, force: true });
494
+ }
495
+ // Stopping the app stopped node --watch and the server under it.
496
+ const port = (_a = status === null || status === void 0 ? void 0 : status.processes.find((process) => process.name === 'functions')) === null || _a === void 0 ? void 0 : _a.port;
497
+ await strict_1.default.rejects(() => fetch(`http://127.0.0.1:${port}/api/hello`));
498
+ });
499
+ });
500
+ (0, node_test_1.describe)('app runner steps', () => {
501
+ let dir;
502
+ const FILES = {
503
+ 'web/package.json': JSON.stringify({ scripts: {} }),
504
+ 'api/server.js': API_SERVER,
505
+ };
506
+ const config = {
507
+ services: {
508
+ web: { root: 'web', installCommand: 'node -e "require(\\"fs\\").writeFileSync(\\"installed\\",\\"\\")"', buildCommand: 'node -e "require(\\"fs\\").writeFileSync(\\"built\\",\\"\\")"', outputDirectory: '.' },
509
+ api: { root: 'api', installCommand: 'node -e "require(\\"fs\\").writeFileSync(\\"installed\\",\\"\\")"', buildCommand: 'node -e "require(\\"fs\\").writeFileSync(\\"built\\",\\"\\")"', startCommand: 'node server.js' },
510
+ },
511
+ rewrites: [{ source: '/api/(.*)', destination: { service: 'api' } }, { source: '/(.*)', destination: { service: 'web' } }],
512
+ };
513
+ const exists = (file) => node_fs_1.promises.access(node_path_1.default.join(dir, file)).then(() => true, () => false);
514
+ (0, node_test_1.before)(async () => {
515
+ dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
516
+ await writeFiles(dir, FILES);
517
+ });
518
+ (0, node_test_1.after)(() => node_fs_1.promises.rm(dir, { recursive: true, force: true }));
519
+ (0, node_test_1.it)('prepare installs and builds every service, and starts nothing', async () => {
520
+ const runner = new runner_1.AppRunner(config, { dir, mode: 'production', env: { PATH: process.env.PATH }, ...FAST });
521
+ strict_1.default.equal(await runner.prepare(), true);
522
+ for (const file of ['web/installed', 'web/built', 'api/installed', 'api/built'])
523
+ strict_1.default.ok(await exists(file), file);
524
+ strict_1.default.deepEqual(runner.status().processes, []);
525
+ await runner.stop();
526
+ });
527
+ (0, node_test_1.it)('a release built ahead of time installs and builds nothing, as it brought both; a static service\'s folder needn\'t be there', async () => {
528
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
529
+ await writeFiles(dir, { 'api/server.js': API_SERVER });
530
+ const runner = new runner_1.AppRunner(config, { dir, mode: 'production', env: { PATH: process.env.PATH }, prebuilt: true, ...FAST });
531
+ strict_1.default.equal(await runner.prepare(), true);
532
+ strict_1.default.ok(!(await exists('api/installed')));
533
+ strict_1.default.ok(!(await exists('api/built')));
534
+ strict_1.default.ok(!(await exists('web')));
535
+ strict_1.default.equal(runner.status().error, undefined);
536
+ await runner.stop();
537
+ });
538
+ (0, node_test_1.it)('a release built ahead of time is starting from the first, and not running until something is', async () => {
539
+ await writeFiles(dir, { 'api/server.js': API_SERVER });
540
+ const runner = new runner_1.AppRunner(config, { dir, mode: 'production', env: { PATH: process.env.PATH }, prebuilt: true, ...FAST });
541
+ strict_1.default.equal(runner.status().phase, 'starting');
542
+ await runner.prepare();
543
+ strict_1.default.equal(runner.status().phase, 'starting');
544
+ await runner.stop();
545
+ });
546
+ (0, node_test_1.it)('a release built ahead of time runs its start script, and not what surrounds it', async () => {
547
+ const mark = (file) => `node -e \\"require('fs').writeFileSync('${file}','')\\"`;
548
+ const files = {
549
+ 'api/server.js': API_SERVER,
550
+ 'api/package.json': `{ "scripts": { "prestart": "${mark('prestarted')}", "start": "${mark('started')} && node server.js" } }`,
551
+ };
552
+ const app = { services: { api: { root: 'api', startCommand: 'npm run start' } } };
553
+ const run = async (prebuilt) => {
554
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
555
+ await writeFiles(dir, files);
556
+ const runner = new runner_1.AppRunner(app, { dir, mode: 'production', env: { PATH: process.env.PATH, HOME: process.env.HOME }, prebuilt, ...FAST });
557
+ await runner.start();
558
+ for (let waited = 0; waited < 100 && runner.status().phase !== 'running'; waited++)
559
+ await new Promise((resolve) => setTimeout(resolve, 100));
560
+ const answer = { phase: runner.status().phase, prestarted: await exists('api/prestarted'), started: await exists('api/started') };
561
+ await runner.stop();
562
+ return answer;
563
+ };
564
+ strict_1.default.deepEqual(await run(true), { phase: 'running', prestarted: false, started: true });
565
+ strict_1.default.deepEqual(await run(false), { phase: 'running', prestarted: true, started: true });
566
+ });
567
+ (0, node_test_1.it)('stop ends a build that is running', async () => {
568
+ const slow = { services: { web: { root: 'slow', buildCommand: 'node -e "setTimeout(() => {}, 60000)"', outputDirectory: '.' } } };
569
+ await writeFiles(dir, { 'slow/index.html': '' });
570
+ const runner = new runner_1.AppRunner(slow, { dir, mode: 'production', env: { PATH: process.env.PATH }, ...FAST });
571
+ const preparing = runner.prepare();
572
+ await new Promise((resolve) => setTimeout(resolve, 300));
573
+ await runner.stop();
574
+ strict_1.default.equal(await preparing, false);
575
+ });
576
+ });
577
+ (0, node_test_1.describe)('app host (deployment, production mode)', () => {
578
+ (0, node_test_1.it)('builds once per release and serves the built output', async () => {
579
+ var _a, _b;
580
+ const dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'builder-app-'));
581
+ const build = `${JSON.stringify(process.execPath)} -e "const fs=require('fs');fs.appendFileSync('builds','x');fs.mkdirSync('dist',{recursive:true});fs.writeFileSync('dist/index.html','built')"`;
582
+ await writeFiles(dir, { '.builder/builder.json': JSON.stringify({ services: { web: { buildCommand: build, outputDirectory: 'dist' } } }) });
583
+ const options = { dir, mode: 'production', env: { PATH: process.env.PATH }, buildId: 'rel1', ...FAST };
584
+ const first = new app_host_1.AppHost(options);
585
+ strict_1.default.equal((_a = (await first.start())) === null || _a === void 0 ? void 0 : _a.phase, 'running');
586
+ const status = await new Promise((resolve) => {
587
+ const res = Object.assign(new node_http_1.default.ServerResponse(new node_http_1.default.IncomingMessage(new node_net_1.default.Socket())), {
588
+ end(body) {
589
+ resolve(body);
590
+ return this;
591
+ },
592
+ });
593
+ first.handleRequest(Object.assign(new node_http_1.default.IncomingMessage(new node_net_1.default.Socket()), { url: protocol_1.APP_STATUS_PATH }), res);
594
+ });
595
+ strict_1.default.equal(JSON.parse(status).app.phase, 'running');
596
+ await first.stop();
597
+ // Restarted on the same disk: the marker skips the build.
598
+ const second = new app_host_1.AppHost(options);
599
+ strict_1.default.equal((_b = (await second.start())) === null || _b === void 0 ? void 0 : _b.phase, 'running');
600
+ strict_1.default.equal(await node_fs_1.promises.readFile(node_path_1.default.join(dir, 'builds'), 'utf8'), 'x');
601
+ await second.stop();
602
+ await node_fs_1.promises.rm(dir, { recursive: true, force: true });
603
+ });
604
+ });
605
+ (0, node_test_1.describe)('a request nothing should be able to answer badly', () => {
606
+ const request = (url) => Object.assign(new node_http_1.default.IncomingMessage(new node_net_1.default.Socket()), { url, method: 'GET', headers: {} });
607
+ (0, node_test_1.it)('never takes the host down: a bad path fails the request, not the guest', async () => {
608
+ const host = new app_host_1.AppHost({ dir: '/nonexistent', mode: 'production', env: {} });
609
+ host.current = { phase: 'running', processes: [] };
610
+ for (const url of ['//', '///', '//[', '//foo:bar']) {
611
+ const res = new node_http_1.default.ServerResponse(request(url));
612
+ strict_1.default.doesNotThrow(() => host.handleRequest(request(url), res), url);
613
+ const socket = new node_net_1.default.Socket();
614
+ strict_1.default.doesNotThrow(() => host.handleUpgrade(request(url), socket, Buffer.alloc(0)), url);
615
+ socket.destroy();
616
+ }
617
+ });
618
+ (0, node_test_1.it)('fails only that request when a handler throws anyway', () => {
619
+ const boom = () => {
620
+ throw new Error('boom');
621
+ };
622
+ const logged = console.error;
623
+ console.error = () => undefined;
624
+ try {
625
+ const req = request('/');
626
+ const res = new node_http_1.default.ServerResponse(req);
627
+ strict_1.default.doesNotThrow(() => (0, preview_proxy_1.guardRequests)(boom)(req, res));
628
+ strict_1.default.equal(res.statusCode, 500);
629
+ strict_1.default.equal(res.writableEnded, true);
630
+ const socket = new node_net_1.default.Socket();
631
+ strict_1.default.doesNotThrow(() => (0, preview_proxy_1.guardUpgrades)(boom)(req, socket, Buffer.alloc(0)));
632
+ strict_1.default.equal(socket.destroyed, true);
633
+ }
634
+ finally {
635
+ console.error = logged;
636
+ }
637
+ });
638
+ });
639
+ //# sourceMappingURL=app-host.test.js.map