@warp-drive/holodeck 0.0.0-alpha.125 → 0.0.0-alpha.127

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { Handler, NextFn, RequestContext, RequestInfo, StructuredDataDocument } from '@ember-data/request';\n\nimport type { ScaffoldGenerator } from './mock';\n\nconst TEST_IDS = new WeakMap<object, { id: string; request: number; mock: number }>();\n\nlet HOST = 'https://localhost:1135/';\nexport function setConfig({ host }: { host: string }) {\n HOST = host.endsWith('/') ? host : `${host}/`;\n}\n\nexport function setTestId(context: object, str: string | null) {\n if (str && TEST_IDS.has(context)) {\n throw new Error(`MockServerHandler is already configured with a testId.`);\n }\n if (str) {\n TEST_IDS.set(context, { id: str, request: 0, mock: 0 });\n } else {\n TEST_IDS.delete(context);\n }\n}\n\nlet IS_RECORDING = false;\nexport function setIsRecording(value: boolean) {\n IS_RECORDING = Boolean(value);\n}\nexport function getIsRecording() {\n return IS_RECORDING;\n}\n\nexport class MockServerHandler implements Handler {\n declare owner: object;\n constructor(owner: object) {\n this.owner = owner;\n }\n async request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>> {\n const test = TEST_IDS.get(this.owner);\n if (!test) {\n throw new Error(\n `MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`\n );\n }\n\n const request: RequestInfo = Object.assign({}, context.request);\n const isRecording = request.url!.endsWith('/__record');\n const firstChar = request.url!.includes('?') ? '&' : '?';\n const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${\n isRecording ? test.mock++ : test.request++\n }`;\n request.url = request.url + queryForTest;\n\n request.mode = 'cors';\n request.credentials = 'omit';\n request.referrerPolicy = '';\n\n try {\n const future = next(request);\n context.setStream(future.getStream());\n return await future;\n } catch (e) {\n if (e instanceof Error && !(e instanceof DOMException)) {\n e.message = e.message.replace(queryForTest, '');\n }\n throw e;\n }\n }\n}\n\nexport async function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean) {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`Cannot call \"mock\" before configuring a testId. Use setTestId to set the testId for each test`);\n }\n const testMockNum = test.mock++;\n if (getIsRecording() || isRecording) {\n const port = window.location.port ? `:${window.location.port}` : '';\n const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;\n await fetch(url, {\n method: 'POST',\n body: JSON.stringify(generate()),\n mode: 'cors',\n credentials: 'omit',\n referrerPolicy: '',\n });\n }\n}\n"],"names":["TEST_IDS","WeakMap","HOST","setConfig","host","endsWith","setTestId","context","str","has","Error","set","id","request","mock","delete","IS_RECORDING","setIsRecording","value","Boolean","getIsRecording","MockServerHandler","constructor","owner","next","test","get","Object","assign","isRecording","url","firstChar","includes","queryForTest","mode","credentials","referrerPolicy","future","setStream","getStream","e","DOMException","message","replace","generate","testMockNum","fetch","method","body","JSON","stringify"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { Handler, NextFn, RequestContext, RequestInfo, StructuredDataDocument } from '@ember-data/request';\n\nimport type { ScaffoldGenerator } from './mock';\n\nconst TEST_IDS = new WeakMap<object, { id: string; request: number; mock: number }>();\n\nlet HOST = 'https://localhost:1135/';\nexport function setConfig({ host }: { host: string }) {\n HOST = host.endsWith('/') ? host : `${host}/`;\n}\n\nexport function setTestId(context: object, str: string | null) {\n if (str && TEST_IDS.has(context)) {\n throw new Error(`MockServerHandler is already configured with a testId.`);\n }\n if (str) {\n TEST_IDS.set(context, { id: str, request: 0, mock: 0 });\n } else {\n TEST_IDS.delete(context);\n }\n}\n\nlet IS_RECORDING = false;\nexport function setIsRecording(value: boolean) {\n IS_RECORDING = Boolean(value);\n}\nexport function getIsRecording() {\n return IS_RECORDING;\n}\n\nexport class MockServerHandler implements Handler {\n declare owner: object;\n constructor(owner: object) {\n this.owner = owner;\n }\n async request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>> {\n const test = TEST_IDS.get(this.owner);\n if (!test) {\n throw new Error(\n `MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`\n );\n }\n\n const request: RequestInfo = Object.assign({}, context.request);\n const isRecording = request.url!.endsWith('/__record');\n const firstChar = request.url!.includes('?') ? '&' : '?';\n const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${\n isRecording ? test.mock++ : test.request++\n }`;\n request.url = request.url + queryForTest;\n\n request.mode = 'cors';\n request.credentials = 'omit';\n request.referrerPolicy = '';\n\n try {\n const future = next(request);\n context.setStream(future.getStream());\n return await future;\n } catch (e) {\n if (e instanceof Error && !(e instanceof DOMException)) {\n e.message = e.message.replace(queryForTest, '');\n }\n throw e;\n }\n }\n}\n\nexport async function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean) {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`Cannot call \"mock\" before configuring a testId. Use setTestId to set the testId for each test`);\n }\n const testMockNum = test.mock++;\n if (getIsRecording() || isRecording) {\n const port = window.location.port ? `:${window.location.port}` : '';\n const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;\n await fetch(url, {\n method: 'POST',\n body: JSON.stringify(generate()),\n mode: 'cors',\n credentials: 'omit',\n referrerPolicy: '',\n });\n }\n}\n"],"names":["TEST_IDS","WeakMap","HOST","setConfig","host","endsWith","setTestId","context","str","has","Error","set","id","request","mock","delete","IS_RECORDING","setIsRecording","value","Boolean","getIsRecording","MockServerHandler","constructor","owner","next","test","get","Object","assign","isRecording","url","firstChar","includes","queryForTest","mode","credentials","referrerPolicy","future","setStream","getStream","e","DOMException","message","replace","generate","testMockNum","fetch","method","body","JSON","stringify"],"mappings":"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"}
package/dist/mock.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mock.js","sources":["../src/mock.ts"],"sourcesContent":["import { getIsRecording, mock } from '.';\n\nexport interface Scaffold {\n status: number;\n statusText?: string;\n headers: Record<string, string>;\n body: Record<string, string> | string | null;\n method: string;\n url: string;\n response: Record<string, unknown>;\n}\n\nexport type ScaffoldGenerator = () => Scaffold;\nexport type ResponseGenerator = () => Record<string, unknown>;\n\n/**\n * Sets up Mocking for a GET request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: whether to record the request (default: false)\n *\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to record the request\n * @return\n */\nexport function GET(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'GET',\n url,\n response: response(),\n }),\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\nexport function POST() {}\nexport function PUT() {}\nexport function PATCH() {}\nexport function DELETE() {}\nexport function QUERY() {}\n"],"names":["GET","owner","url","response","options","mock","status","statusText","headers","body","method","getIsRecording","RECORD","POST","PUT","PATCH","DELETE","QUERY"],"mappings":";;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,GAAGA,CACjBC,KAAa,EACbC,GAAW,EACXC,QAA2B,EAC3BC,OAAuF,EACxE;AACf,EAAA,OAAOC,IAAI,CACTJ,KAAK,EACL,OAAO;AACLK,IAAAA,MAAM,EAAEF,OAAO,EAAEE,MAAM,IAAI,GAAG;AAC9BC,IAAAA,UAAU,EAAEH,OAAO,EAAEG,UAAU,IAAI,IAAI;AACvCC,IAAAA,OAAO,EAAEJ,OAAO,EAAEI,OAAO,IAAI,EAAE;AAC/BC,IAAAA,IAAI,EAAEL,OAAO,EAAEK,IAAI,IAAI,IAAI;AAC3BC,IAAAA,MAAM,EAAE,KAAK;IACbR,GAAG;IACHC,QAAQ,EAAEA,QAAQ,EAAC;AACrB,GAAC,CAAC,EACFQ,cAAc,EAAE,KAAKP,OAAO,EAAEQ,MAAM,IAAI,KAAK,CAC/C,CAAC,CAAA;AACH,CAAA;AACO,SAASC,IAAIA,GAAG,EAAC;AACjB,SAASC,GAAGA,GAAG,EAAC;AAChB,SAASC,KAAKA,GAAG,EAAC;AAClB,SAASC,MAAMA,GAAG,EAAC;AACnB,SAASC,KAAKA,GAAG;;;;"}
1
+ {"version":3,"file":"mock.js","sources":["../src/mock.ts"],"sourcesContent":["import { getIsRecording, mock } from '.';\n\nexport interface Scaffold {\n status: number;\n statusText?: string;\n headers: Record<string, string>;\n body: Record<string, string> | string | null;\n method: string;\n url: string;\n response: Record<string, unknown>;\n}\n\nexport type ScaffoldGenerator = () => Scaffold;\nexport type ResponseGenerator = () => Record<string, unknown>;\n\n/**\n * Sets up Mocking for a GET request on the mock server\n * for the supplied url.\n *\n * The response body is generated by the supplied response function.\n *\n * Available options:\n * - status: the status code to return (default: 200)\n * - headers: the headers to return (default: {})\n * - body: the body to match against for the request (default: null)\n * - RECORD: whether to record the request (default: false)\n *\n * @param url the url to mock, relative to the mock server host (e.g. `users/1`)\n * @param response a function which generates the response to return\n * @param options status, headers for the response, body to match against for the request, and whether to record the request\n * @return\n */\nexport function GET(\n owner: object,\n url: string,\n response: ResponseGenerator,\n options?: Partial<Omit<Scaffold, 'response' | 'url' | 'method'>> & { RECORD?: boolean }\n): Promise<void> {\n return mock(\n owner,\n () => ({\n status: options?.status ?? 200,\n statusText: options?.statusText ?? 'OK',\n headers: options?.headers ?? {},\n body: options?.body ?? null,\n method: 'GET',\n url,\n response: response(),\n }),\n getIsRecording() || (options?.RECORD ?? false)\n );\n}\nexport function POST() {}\nexport function PUT() {}\nexport function PATCH() {}\nexport function DELETE() {}\nexport function QUERY() {}\n"],"names":["GET","owner","url","response","options","mock","status","statusText","headers","body","method","getIsRecording","RECORD","POST","PUT","PATCH","DELETE","QUERY"],"mappings":";;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,GAAGA,CACjBC,KAAa,EACbC,GAAW,EACXC,QAA2B,EAC3BC,OAAuF,EACxE;AACf,EAAA,OAAOC,IAAI,CACTJ,KAAK,EACL,OAAO;AACLK,IAAAA,MAAM,EAAEF,OAAO,EAAEE,MAAM,IAAI,GAAG;AAC9BC,IAAAA,UAAU,EAAEH,OAAO,EAAEG,UAAU,IAAI,IAAI;AACvCC,IAAAA,OAAO,EAAEJ,OAAO,EAAEI,OAAO,IAAI,EAAE;AAC/BC,IAAAA,IAAI,EAAEL,OAAO,EAAEK,IAAI,IAAI,IAAI;AAC3BC,IAAAA,MAAM,EAAE,KAAK;IACbR,GAAG;IACHC,QAAQ,EAAEA,QAAQ;AACpB,GAAC,CAAC,EACFQ,cAAc,EAAE,KAAKP,OAAO,EAAEQ,MAAM,IAAI,KAAK,CAC/C,CAAC;AACH;AACO,SAASC,IAAIA,GAAG;AAChB,SAASC,GAAGA,GAAG;AACf,SAASC,KAAKA,GAAG;AACjB,SAASC,MAAMA,GAAG;AAClB,SAASC,KAAKA,GAAG;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@warp-drive/holodeck",
3
3
  "description": "⚡️ Simple, Fast HTTP Mocking for Tests",
4
- "version": "0.0.0-alpha.125",
4
+ "version": "0.0.0-alpha.127",
5
5
  "license": "MIT",
6
6
  "author": "Chris Thoburn <runspired@users.noreply.github.com>",
7
7
  "repository": {
@@ -12,7 +12,7 @@
12
12
  "homepage": "https://github.com/emberjs/data",
13
13
  "bugs": "https://github.com/emberjs/data/issues",
14
14
  "engines": {
15
- "node": ">= 18.20.4"
15
+ "node": ">= 18.20.7"
16
16
  },
17
17
  "keywords": [
18
18
  "http-mock"
@@ -21,9 +21,9 @@
21
21
  "extends": "../../package.json"
22
22
  },
23
23
  "dependencies": {
24
- "@hono/node-server": "^1.11.1",
25
24
  "chalk": "^5.3.0",
26
- "hono": "^4.7.0"
25
+ "hono": "^4.7.2",
26
+ "@hono/node-server": "^1.13.8"
27
27
  },
28
28
  "type": "module",
29
29
  "files": [
@@ -39,8 +39,8 @@
39
39
  "ensure-cert": "./server/ensure-cert.js"
40
40
  },
41
41
  "peerDependencies": {
42
- "@ember-data/request": "5.4.0-alpha.139",
43
- "@warp-drive/core-types": "5.4.0-alpha.139"
42
+ "@ember-data/request": "5.4.0-alpha.141",
43
+ "@warp-drive/core-types": "5.4.0-alpha.141"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@babel/core": "^7.24.5",
@@ -48,12 +48,10 @@
48
48
  "@babel/preset-env": "^7.24.5",
49
49
  "@babel/preset-typescript": "^7.24.1",
50
50
  "@babel/runtime": "^7.24.5",
51
- "@ember-data/request": "5.4.0-alpha.139",
52
- "@warp-drive/core-types": "5.4.0-alpha.139",
53
- "@warp-drive/internal-config": "5.4.0-alpha.139",
54
- "pnpm-sync-dependencies-meta-injected": "0.0.14",
55
- "typescript": "^5.7.2",
56
- "vite": "^5.2.11"
51
+ "@ember-data/request": "5.4.0-alpha.141",
52
+ "@warp-drive/core-types": "5.4.0-alpha.141",
53
+ "@warp-drive/internal-config": "5.4.0-alpha.141",
54
+ "vite": "^5.4.14"
57
55
  },
58
56
  "exports": {
59
57
  ".": {
@@ -72,17 +70,9 @@
72
70
  "default": "./dist/mock.js"
73
71
  }
74
72
  },
75
- "dependenciesMeta": {
76
- "@ember-data/request": {
77
- "injected": true
78
- },
79
- "@warp-drive/core-types": {
80
- "injected": true
81
- }
82
- },
83
73
  "scripts": {
84
74
  "check:pkg-types": "tsc --noEmit",
85
75
  "build:pkg": "vite build;",
86
- "sync-hardlinks": "bun run sync-dependencies-meta-injected"
76
+ "sync": "echo \"syncing\""
87
77
  }
88
78
  }
package/server/index.js CHANGED
@@ -1,23 +1,22 @@
1
1
  /* global Bun */
2
- import { serve } from '@hono/node-server';
3
2
  import chalk from 'chalk';
4
3
  import { Hono } from 'hono';
5
- import { cors } from 'hono/cors';
6
- import { HTTPException } from 'hono/http-exception';
4
+ import { serve } from '@hono/node-server';
5
+ import { createSecureServer } from 'node:http2';
7
6
  import { logger } from 'hono/logger';
7
+ import { HTTPException } from 'hono/http-exception';
8
+ import { cors } from 'hono/cors';
8
9
  import crypto from 'node:crypto';
9
10
  import fs from 'node:fs';
10
- import http2 from 'node:http2';
11
11
  import zlib from 'node:zlib';
12
12
  import { homedir } from 'os';
13
13
  import path from 'path';
14
14
 
15
- /** @type {import('bun-types')} */
16
15
  const isBun = typeof Bun !== 'undefined';
17
- const DEBUG = process.env.DEBUG?.includes('holodeck') || process.env.DEBUG === '*';
18
- const CURRENT_FILE = new URL(import.meta.url).pathname;
16
+ const DEBUG =
17
+ process.env.DEBUG?.includes('wd:holodeck') || process.env.DEBUG === '*' || process.env.DEBUG?.includes('wd:*');
19
18
 
20
- function getCertInfo() {
19
+ async function getCertInfo() {
21
20
  let CERT_PATH = process.env.HOLODECK_SSL_CERT_PATH;
22
21
  let KEY_PATH = process.env.HOLODECK_SSL_KEY_PATH;
23
22
 
@@ -39,16 +38,32 @@ function getCertInfo() {
39
38
  );
40
39
  }
41
40
 
42
- if (!fs.existsSync(CERT_PATH) || !fs.existsSync(KEY_PATH)) {
43
- throw new Error('SSL certificate or key not found, you may need to run `pnpx @warp-drive/holodeck ensure-cert`');
44
- }
41
+ if (isBun) {
42
+ const CERT = Bun.file(CERT_PATH);
43
+ const KEY = Bun.file(KEY_PATH);
45
44
 
46
- return {
47
- CERT_PATH,
48
- KEY_PATH,
49
- CERT: fs.readFileSync(CERT_PATH),
50
- KEY: fs.readFileSync(KEY_PATH),
51
- };
45
+ if (!(await CERT.exists()) || !(await KEY.exists())) {
46
+ throw new Error('SSL certificate or key not found, you may need to run `pnpx @warp-drive/holodeck ensure-cert`');
47
+ }
48
+
49
+ return {
50
+ CERT_PATH,
51
+ KEY_PATH,
52
+ CERT: await CERT.text(),
53
+ KEY: await KEY.text(),
54
+ };
55
+ } else {
56
+ if (!fs.existsSync(CERT_PATH) || !fs.existsSync(KEY_PATH)) {
57
+ throw new Error('SSL certificate or key not found, you may need to run `pnpx @warp-drive/holodeck ensure-cert`');
58
+ }
59
+
60
+ return {
61
+ CERT_PATH,
62
+ KEY_PATH,
63
+ CERT: fs.readFileSync(CERT_PATH, 'utf8'),
64
+ KEY: fs.readFileSync(KEY_PATH, 'utf8'),
65
+ };
66
+ }
52
67
  }
53
68
 
54
69
  const DEFAULT_PORT = 1135;
@@ -85,7 +100,7 @@ function getNiceUrl(url) {
85
100
  */
86
101
  function generateFilepath(options) {
87
102
  const { body } = options;
88
- const bodyHash = body ? crypto.createHash('md5').update(body).digest('hex') : null;
103
+ const bodyHash = body ? crypto.createHash('md5').update(JSON.stringify(body)).digest('hex') : null;
89
104
  const cacheDir = generateFileDir(options);
90
105
  return `${cacheDir}/${bodyHash ? `${bodyHash}-` : 'res'}`;
91
106
  }
@@ -94,10 +109,14 @@ function generateFileDir(options) {
94
109
  return `${projectRoot}/.mock-cache/${testId}/${method}-${testRequestNumber}-${url}`;
95
110
  }
96
111
 
97
- function replayRequest(context, cacheKey) {
98
- let meta;
112
+ async function replayRequest(context, cacheKey) {
113
+ let metaJson;
99
114
  try {
100
- meta = fs.readFileSync(`${cacheKey}.meta.json`, 'utf-8');
115
+ if (isBun) {
116
+ metaJson = await Bun.file(`${cacheKey}.meta.json`).json();
117
+ } else {
118
+ metaJson = JSON.parse(fs.readFileSync(`${cacheKey}.meta.json`, 'utf8'));
119
+ }
101
120
  } catch (e) {
102
121
  context.header('Content-Type', 'application/vnd.api+json');
103
122
  context.status(400);
@@ -108,18 +127,23 @@ function replayRequest(context, cacheKey) {
108
127
  status: '400',
109
128
  code: 'MOCK_NOT_FOUND',
110
129
  title: 'Mock not found',
111
- detail: `No mock found for ${context.req.method} ${context.req.url}. You may need to record a mock for this request.`,
130
+ detail: `No meta was found for ${context.req.method} ${context.req.url}. You may need to record a mock for this request.`,
112
131
  },
113
132
  ],
114
133
  })
115
134
  );
116
135
  }
117
136
 
118
- const metaJson = JSON.parse(meta);
119
137
  const bodyPath = `${cacheKey}.body.br`;
138
+ const bodyInit =
139
+ metaJson.status !== 204 && metaJson.status < 500
140
+ ? isBun
141
+ ? Bun.file(bodyPath)
142
+ : fs.createReadStream(bodyPath)
143
+ : '';
120
144
 
121
145
  const headers = new Headers(metaJson.headers || {});
122
- const bodyInit = metaJson.status !== 204 && metaJson.status < 500 ? fs.createReadStream(bodyPath) : '';
146
+ // @ts-expect-error - createReadStream is supported in node
123
147
  const response = new Response(bodyInit, {
124
148
  status: metaJson.status,
125
149
  statusText: metaJson.statusText,
@@ -191,12 +215,15 @@ function createTestHandler(projectRoot) {
191
215
  body: body ? JSON.stringify(body) : null,
192
216
  testRequestNumber,
193
217
  });
218
+ const compressedResponse = compress(JSON.stringify(response));
194
219
  // allow Content-Type to be overridden
195
220
  headers['Content-Type'] = headers['Content-Type'] || 'application/vnd.api+json';
196
221
  // We always compress and chunk the response
197
222
  headers['Content-Encoding'] = 'br';
198
223
  // we don't cache since tests will often reuse similar urls for different payload
199
224
  headers['Cache-Control'] = 'no-store';
225
+ // streaming requires Content-Length
226
+ headers['Content-Length'] = compressedResponse.length;
200
227
 
201
228
  const cacheDir = generateFileDir({
202
229
  projectRoot,
@@ -207,21 +234,30 @@ function createTestHandler(projectRoot) {
207
234
  });
208
235
 
209
236
  fs.mkdirSync(cacheDir, { recursive: true });
210
- fs.writeFileSync(
211
- `${cacheKey}.meta.json`,
212
- JSON.stringify({ url, status, statusText, headers, method, requestBody: body }, null, 2)
213
- );
214
- fs.writeFileSync(`${cacheKey}.body.br`, compress(JSON.stringify(response)));
237
+
238
+ if (isBun) {
239
+ const newMetaFile = Bun.file(`${cacheKey}.meta.json`);
240
+ await newMetaFile.write(JSON.stringify({ url, status, statusText, headers, method, requestBody: body }));
241
+ const newBodyFile = Bun.file(`${cacheKey}.body.br`);
242
+ await newBodyFile.write(compressedResponse);
243
+ } else {
244
+ fs.writeFileSync(
245
+ `${cacheKey}.meta.json`,
246
+ JSON.stringify({ url, status, statusText, headers, method, requestBody: body })
247
+ );
248
+ fs.writeFileSync(`${cacheKey}.body.br`, compressedResponse);
249
+ }
250
+
215
251
  context.status(204);
216
252
  return context.body(null);
217
253
  } else {
218
- const body = await req.text();
254
+ const body = req.body;
219
255
  const cacheKey = generateFilepath({
220
256
  projectRoot,
221
257
  testId,
222
258
  url: niceUrl,
223
259
  method: req.method,
224
- body,
260
+ body: body ? JSON.stringify(body) : null,
225
261
  testRequestNumber,
226
262
  });
227
263
  return replayRequest(context, cacheKey);
@@ -250,10 +286,68 @@ function createTestHandler(projectRoot) {
250
286
  return TestHandler;
251
287
  }
252
288
 
289
+ export function startNodeServer() {
290
+ const args = process.argv.slice();
291
+
292
+ if (!isBun && args.length) {
293
+ const options = JSON.parse(args[2]);
294
+ _createServer(options);
295
+ }
296
+ }
297
+
298
+ export function startWorker() {
299
+ // listen for launch message
300
+ globalThis.onmessage = async (event) => {
301
+ const { options } = event.data;
302
+
303
+ const { server } = await _createServer(options);
304
+
305
+ // listen for messages
306
+ globalThis.onmessage = (event) => {
307
+ const message = event.data;
308
+ if (message === 'end') {
309
+ server.close();
310
+ globalThis.close();
311
+ }
312
+ };
313
+ };
314
+ }
315
+
253
316
  /*
254
317
  { port?: number, projectRoot: string }
255
318
  */
256
- export function createServer(options) {
319
+ export async function createServer(options, useBun = false) {
320
+ if (!useBun) {
321
+ const CURRENT_FILE = new URL(import.meta.url).pathname;
322
+ const START_FILE = path.join(CURRENT_FILE, '../start-node.js');
323
+ const server = Bun.spawn(['node', '--experimental-default-type=module', START_FILE, JSON.stringify(options)], {
324
+ env: process.env,
325
+ cwd: process.cwd(),
326
+ stdin: 'inherit',
327
+ stdout: 'inherit',
328
+ stderr: 'inherit',
329
+ });
330
+
331
+ return {
332
+ terminate() {
333
+ server.kill();
334
+ // server.unref();
335
+ },
336
+ };
337
+ }
338
+
339
+ const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
340
+
341
+ worker.postMessage({
342
+ type: 'launch',
343
+ options,
344
+ });
345
+
346
+ return worker;
347
+ }
348
+
349
+ async function _createServer(options) {
350
+ const { CERT, KEY } = await getCertInfo();
257
351
  const app = new Hono();
258
352
  if (DEBUG) {
259
353
  app.use('*', logger());
@@ -272,38 +366,26 @@ export function createServer(options) {
272
366
  );
273
367
  app.all('*', createTestHandler(options.projectRoot));
274
368
 
275
- const { CERT, KEY } = getCertInfo();
276
-
277
- serve({
369
+ const server = serve({
370
+ overrideGlobalObjects: !isBun,
278
371
  fetch: app.fetch,
279
- createServer: (_, requestListener) => {
280
- try {
281
- return http2.createSecureServer(
282
- {
283
- key: KEY,
284
- cert: CERT,
285
- },
286
- requestListener
287
- );
288
- } catch (e) {
289
- console.log(chalk.yellow(`Failed to create secure server, falling back to http server. Error: ${e.message}`));
290
- return http2.createServer(requestListener);
291
- }
372
+ serverOptions: {
373
+ key: KEY,
374
+ cert: CERT,
292
375
  },
376
+ createServer: createSecureServer,
293
377
  port: options.port ?? DEFAULT_PORT,
294
- hostname: 'localhost',
295
- // bun uses TLS options
296
- // tls: {
297
- // key: Bun.file(KEY_PATH),
298
- // cert: Bun.file(CERT_PATH),
299
- // },
378
+ hostname: options.hostname ?? 'localhost',
300
379
  });
301
380
 
302
381
  console.log(
303
- `\tMock server running at ${chalk.magenta('https://localhost:') + chalk.yellow(options.port ?? DEFAULT_PORT)}`
382
+ `\tMock server running at ${chalk.yellow('https://') + chalk.magenta((options.hostname ?? 'localhost') + ':') + chalk.yellow(options.port ?? DEFAULT_PORT)}`
304
383
  );
384
+
385
+ return { app, server };
305
386
  }
306
387
 
388
+ /** @type {Map<string, Awaited<ReturnType<typeof createServer>>>} */
307
389
  const servers = new Map();
308
390
 
309
391
  export default {
@@ -325,59 +407,29 @@ export default {
325
407
  )}`
326
408
  )
327
409
  );
328
- console.log(chalk.grey(`\n\tStarting Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
329
-
330
- if (isBun) {
331
- const serverProcess = Bun.spawn(
332
- ['node', '--experimental-default-type=module', CURRENT_FILE, JSON.stringify(options)],
333
- {
334
- env: process.env,
335
- cwd: process.cwd(),
336
- stdout: 'inherit',
337
- stderr: 'inherit',
338
- }
339
- );
340
- servers.set(projectRoot, serverProcess);
341
- return;
342
- }
410
+ console.log(chalk.grey(`\n\tStarting Holodeck Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
343
411
 
344
412
  if (servers.has(projectRoot)) {
345
413
  throw new Error(`Holodeck is already running for project '${name}' at '${projectRoot}'`);
346
414
  }
347
415
 
348
- servers.set(projectRoot, createServer(options));
416
+ // toggle to true if Bun fixes CORS support for HTTP/2
417
+ const project = await createServer(options, false);
418
+ servers.set(projectRoot, project);
349
419
  },
350
420
  async endProgram() {
351
- console.log(chalk.grey(`\n\tEnding Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
421
+ console.log(chalk.grey(`\n\tEnding Holodeck Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
352
422
  const projectRoot = process.cwd();
353
423
 
354
424
  if (!servers.has(projectRoot)) {
355
- const name = await import(path.join(projectRoot, 'package.json'), { with: { type: 'json' } }).then(
356
- (pkg) => pkg.name
357
- );
358
- throw new Error(`Holodeck was not running for project '${name}' at '${projectRoot}'`);
359
- }
360
-
361
- if (isBun) {
362
- const serverProcess = servers.get(projectRoot);
363
- serverProcess.kill();
425
+ const name = require(path.join(projectRoot, 'package.json')).name;
426
+ console.log(chalk.red(`\n\nHolodeck was not running for project '${name}' at '${projectRoot}'\n\n`));
364
427
  return;
365
428
  }
366
429
 
367
- servers.get(projectRoot).close();
430
+ const project = servers.get(projectRoot);
368
431
  servers.delete(projectRoot);
432
+ project.terminate();
433
+ console.log(chalk.grey(`\n\tHolodeck program ended`));
369
434
  },
370
435
  };
371
-
372
- function main() {
373
- const args = process.argv.slice();
374
- if (!isBun && args.length) {
375
- if (args[1] !== CURRENT_FILE) {
376
- return;
377
- }
378
- const options = JSON.parse(args[2]);
379
- createServer(options);
380
- }
381
- }
382
-
383
- main();
@@ -0,0 +1,3 @@
1
+ import { startNodeServer } from './index.js';
2
+
3
+ startNodeServer();
@@ -0,0 +1,12 @@
1
+ {
2
+ "include": ["."],
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "types": ["bun-types"],
8
+ "allowJs": true,
9
+ "checkJs": true,
10
+ "noEmit": true
11
+ }
12
+ }
@@ -0,0 +1,3 @@
1
+ import { startWorker } from './index.js';
2
+
3
+ startWorker();