@warp-drive/holodeck 0.0.0-alpha.44 → 0.0.0-alpha.45
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.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -9
- package/server/ensure-cert.js +55 -0
- package/server/index.js +110 -16
- package/bin/cmd/_start.js +0 -3
- package/bin/cmd/_stop.js +0 -3
- package/bin/cmd/pm2.js +0 -39
- package/bin/cmd/run.js +0 -50
- package/bin/cmd/spawn.js +0 -40
- package/bin/holodeck.js +0 -64
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Handler, NextFn, RequestContext, StructuredDataDocument } from '@ember-data/request';
|
|
2
2
|
import type { ScaffoldGenerator } from './mock';
|
|
3
|
+
export declare function setConfig({ host }: {
|
|
4
|
+
host: string;
|
|
5
|
+
}): void;
|
|
3
6
|
export declare function setTestId(context: object, str: string | null): void;
|
|
4
7
|
export declare function setIsRecording(value: boolean): void;
|
|
5
8
|
export declare function getIsRecording(): boolean;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAe,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEhH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAe,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEhH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAKhD,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,QAEnD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,QAS5D;AAGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,QAE5C;AACD,wBAAgB,cAAc,YAE7B;AAED,qBAAa,iBAAkB,YAAW,OAAO;IACvC,KAAK,EAAE,MAAM,CAAC;gBACV,KAAK,EAAE,MAAM;IAGnB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;CA+B/F;AAED,wBAAsB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,OAAO,iBAiB3F"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
const TEST_IDS = new WeakMap();
|
|
2
|
+
let HOST = 'https://localhost:1135/';
|
|
3
|
+
function setConfig({
|
|
4
|
+
host
|
|
5
|
+
}) {
|
|
6
|
+
HOST = host.endsWith('/') ? host : `${host}/`;
|
|
7
|
+
}
|
|
2
8
|
function setTestId(context, str) {
|
|
3
9
|
if (str && TEST_IDS.has(context)) {
|
|
4
10
|
throw new Error(`MockServerHandler is already configured with a testId.`);
|
|
@@ -56,7 +62,7 @@ async function mock(owner, generate, isRecording) {
|
|
|
56
62
|
}
|
|
57
63
|
const testMockNum = test.mock++;
|
|
58
64
|
if (getIsRecording() || isRecording) {
|
|
59
|
-
const url =
|
|
65
|
+
const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
|
|
60
66
|
await fetch(url, {
|
|
61
67
|
method: 'POST',
|
|
62
68
|
body: JSON.stringify(generate()),
|
|
@@ -67,5 +73,5 @@ async function mock(owner, generate, isRecording) {
|
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
export { MockServerHandler, getIsRecording, mock, setIsRecording, setTestId };
|
|
76
|
+
export { MockServerHandler, getIsRecording, mock, setConfig, setIsRecording, setTestId };
|
|
71
77
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../client/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\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
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../client/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/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.
|
|
4
|
+
"version": "0.0.0-alpha.45",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chris Thoburn <runspired@users.noreply.github.com>",
|
|
7
7
|
"repository": {
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@hono/node-server": "^1.10.0",
|
|
25
25
|
"chalk": "^5.3.0",
|
|
26
|
-
"hono": "^4.2.4"
|
|
27
|
-
"pm2": "^5.3.1"
|
|
26
|
+
"hono": "^4.2.4"
|
|
28
27
|
},
|
|
29
28
|
"files": [
|
|
30
29
|
"bin",
|
|
@@ -36,7 +35,7 @@
|
|
|
36
35
|
"NCC-1701-a-blue.svg"
|
|
37
36
|
],
|
|
38
37
|
"bin": {
|
|
39
|
-
"
|
|
38
|
+
"ensure-cert": "./server/ensure-cert.js"
|
|
40
39
|
},
|
|
41
40
|
"scripts": {
|
|
42
41
|
"build:types": "echo \"Types are private\" && exit 0",
|
|
@@ -46,8 +45,8 @@
|
|
|
46
45
|
"_syncPnpm": "bun run sync-dependencies-meta-injected"
|
|
47
46
|
},
|
|
48
47
|
"peerDependencies": {
|
|
49
|
-
"@ember-data/request": "5.4.0-alpha.
|
|
50
|
-
"@warp-drive/core-types": "0.0.0-alpha.
|
|
48
|
+
"@ember-data/request": "5.4.0-alpha.59",
|
|
49
|
+
"@warp-drive/core-types": "0.0.0-alpha.45"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
53
52
|
"@babel/cli": "^7.24.1",
|
|
@@ -56,12 +55,12 @@
|
|
|
56
55
|
"@babel/preset-env": "^7.24.4",
|
|
57
56
|
"@babel/preset-typescript": "^7.24.1",
|
|
58
57
|
"@babel/runtime": "^7.24.4",
|
|
59
|
-
"@ember-data/request": "5.4.0-alpha.
|
|
58
|
+
"@ember-data/request": "5.4.0-alpha.59",
|
|
60
59
|
"@embroider/addon-dev": "^4.3.1",
|
|
61
60
|
"@rollup/plugin-babel": "^6.0.4",
|
|
62
61
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
63
|
-
"@warp-drive/core-types": "0.0.0-alpha.
|
|
64
|
-
"@warp-drive/internal-config": "5.4.0-alpha.
|
|
62
|
+
"@warp-drive/core-types": "0.0.0-alpha.45",
|
|
63
|
+
"@warp-drive/internal-config": "5.4.0-alpha.59",
|
|
65
64
|
"rollup": "^4.14.3",
|
|
66
65
|
"typescript": "^5.4.5",
|
|
67
66
|
"walk-sync": "^3.0.0",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { homedir, userInfo } from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
function getShellConfigFilePath() {
|
|
8
|
+
const shell = userInfo().shell;
|
|
9
|
+
switch (shell) {
|
|
10
|
+
case '/bin/zsh':
|
|
11
|
+
return path.join(homedir(), '.zshrc');
|
|
12
|
+
case '/bin/bash':
|
|
13
|
+
return path.join(homedir(), '.bashrc');
|
|
14
|
+
default:
|
|
15
|
+
throw Error(
|
|
16
|
+
`Unable to determine configuration file for shell: ${shell}. Manual SSL Cert Setup Required for Holodeck.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function main() {
|
|
22
|
+
let CERT_PATH = process.env.HOLODECK_SSL_CERT_PATH;
|
|
23
|
+
let KEY_PATH = process.env.HOLODECK_SSL_KEY_PATH;
|
|
24
|
+
const configFilePath = getShellConfigFilePath();
|
|
25
|
+
|
|
26
|
+
if (!CERT_PATH) {
|
|
27
|
+
CERT_PATH = path.join(homedir(), 'holodeck-localhost.pem');
|
|
28
|
+
process.env.HOLODECK_SSL_CERT_PATH = CERT_PATH;
|
|
29
|
+
execSync(`echo '\nexport HOLODECK_SSL_CERT_PATH="${CERT_PATH}"' >> ${configFilePath}`);
|
|
30
|
+
console.log(`Added HOLODECK_SSL_CERT_PATH to ${configFilePath}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!KEY_PATH) {
|
|
34
|
+
KEY_PATH = path.join(homedir(), 'holodeck-localhost-key.pem');
|
|
35
|
+
process.env.HOLODECK_SSL_KEY_PATH = KEY_PATH;
|
|
36
|
+
execSync(`echo '\nexport HOLODECK_SSL_KEY_PATH="${KEY_PATH}"' >> ${configFilePath}`);
|
|
37
|
+
console.log(`Added HOLODECK_SSL_KEY_PATH to ${configFilePath}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(CERT_PATH) || !fs.existsSync(KEY_PATH)) {
|
|
41
|
+
console.log('SSL certificate or key not found, generating new ones...');
|
|
42
|
+
|
|
43
|
+
execSync(`mkcert -install`);
|
|
44
|
+
execSync(`mkcert -key-file ${KEY_PATH} -cert-file ${CERT_PATH} localhost`);
|
|
45
|
+
|
|
46
|
+
console.log('SSL certificate and key generated.');
|
|
47
|
+
} else {
|
|
48
|
+
console.log('SSL certificate and key found, using existing.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`Certificate path: ${CERT_PATH}`);
|
|
52
|
+
console.log(`Key path: ${KEY_PATH}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main();
|
package/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* global Bun */
|
|
1
2
|
import { serve } from '@hono/node-server';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
4
|
import { Hono } from 'hono';
|
|
@@ -12,6 +13,11 @@ import zlib from 'node:zlib';
|
|
|
12
13
|
import { homedir, userInfo } from 'os';
|
|
13
14
|
import path from 'path';
|
|
14
15
|
|
|
16
|
+
/** @type {import('bun-types')} */
|
|
17
|
+
const isBun = typeof Bun !== 'undefined';
|
|
18
|
+
const DEBUG = process.env.DEBUG?.includes('holodeck') || process.env.DEBUG === '*';
|
|
19
|
+
const CURRENT_FILE = new URL(import.meta.url).pathname;
|
|
20
|
+
|
|
15
21
|
function getShellConfigFilePath() {
|
|
16
22
|
const shell = userInfo().shell;
|
|
17
23
|
switch (shell) {
|
|
@@ -29,26 +35,24 @@ function getShellConfigFilePath() {
|
|
|
29
35
|
function getCertInfo() {
|
|
30
36
|
let CERT_PATH = process.env.HOLODECK_SSL_CERT_PATH;
|
|
31
37
|
let KEY_PATH = process.env.HOLODECK_SSL_KEY_PATH;
|
|
38
|
+
const configFilePath = getShellConfigFilePath();
|
|
32
39
|
|
|
33
40
|
if (!CERT_PATH) {
|
|
34
41
|
CERT_PATH = path.join(homedir(), 'holodeck-localhost.pem');
|
|
35
42
|
process.env.HOLODECK_SSL_CERT_PATH = CERT_PATH;
|
|
36
|
-
execSync(`echo '\nexport HOLODECK_SSL_CERT_PATH="${CERT_PATH}"' >> ${
|
|
37
|
-
console.log(`Added HOLODECK_SSL_CERT_PATH to ${
|
|
43
|
+
execSync(`echo '\nexport HOLODECK_SSL_CERT_PATH="${CERT_PATH}"' >> ${configFilePath}`);
|
|
44
|
+
console.log(`Added HOLODECK_SSL_CERT_PATH to ${configFilePath}`);
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
if (!KEY_PATH) {
|
|
41
48
|
KEY_PATH = path.join(homedir(), 'holodeck-localhost-key.pem');
|
|
42
49
|
process.env.HOLODECK_SSL_KEY_PATH = KEY_PATH;
|
|
43
|
-
execSync(`echo '\nexport HOLODECK_SSL_KEY_PATH="${KEY_PATH}"' >> ${
|
|
44
|
-
console.log(`Added HOLODECK_SSL_KEY_PATH to ${
|
|
50
|
+
execSync(`echo '\nexport HOLODECK_SSL_KEY_PATH="${KEY_PATH}"' >> ${configFilePath}`);
|
|
51
|
+
console.log(`Added HOLODECK_SSL_KEY_PATH to ${configFilePath}`);
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
if (!fs.existsSync(CERT_PATH) || !fs.existsSync(KEY_PATH)) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
execSync(`mkcert -install`);
|
|
51
|
-
execSync(`mkcert -key-file ${KEY_PATH} -cert-file ${CERT_PATH} localhost`);
|
|
55
|
+
throw new Error('SSL certificate or key not found, you may need to run `npx -p @warp-drive/holodeck ensure-cert`');
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
return {
|
|
@@ -243,7 +247,9 @@ function createTestHandler(projectRoot) {
|
|
|
243
247
|
*/
|
|
244
248
|
export function createServer(options) {
|
|
245
249
|
const app = new Hono();
|
|
246
|
-
|
|
250
|
+
if (DEBUG) {
|
|
251
|
+
app.use('*', logger());
|
|
252
|
+
}
|
|
247
253
|
app.use(
|
|
248
254
|
'*',
|
|
249
255
|
cors({
|
|
@@ -263,19 +269,107 @@ export function createServer(options) {
|
|
|
263
269
|
serve({
|
|
264
270
|
fetch: app.fetch,
|
|
265
271
|
createServer: (_, requestListener) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
try {
|
|
273
|
+
return http2.createSecureServer(
|
|
274
|
+
{
|
|
275
|
+
key: KEY,
|
|
276
|
+
cert: CERT,
|
|
277
|
+
},
|
|
278
|
+
requestListener
|
|
279
|
+
);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
console.log(chalk.yellow(`Failed to create secure server, falling back to http server. Error: ${e.message}`));
|
|
282
|
+
return http2.createServer(requestListener);
|
|
283
|
+
}
|
|
273
284
|
},
|
|
274
285
|
port: options.port ?? DEFAULT_PORT,
|
|
275
286
|
hostname: 'localhost',
|
|
287
|
+
// bun uses TLS options
|
|
288
|
+
// tls: {
|
|
289
|
+
// key: Bun.file(KEY_PATH),
|
|
290
|
+
// cert: Bun.file(CERT_PATH),
|
|
291
|
+
// },
|
|
276
292
|
});
|
|
277
293
|
|
|
278
294
|
console.log(
|
|
279
295
|
`\tMock server running at ${chalk.magenta('https://localhost:') + chalk.yellow(options.port ?? DEFAULT_PORT)}`
|
|
280
296
|
);
|
|
281
297
|
}
|
|
298
|
+
|
|
299
|
+
const servers = new Map();
|
|
300
|
+
|
|
301
|
+
export default {
|
|
302
|
+
async launchProgram(config = {}) {
|
|
303
|
+
const projectRoot = process.cwd();
|
|
304
|
+
const name = await import(path.join(projectRoot, 'package.json'), { with: { type: 'json' } }).then(
|
|
305
|
+
(pkg) => pkg.name
|
|
306
|
+
);
|
|
307
|
+
const options = { name, projectRoot, ...config };
|
|
308
|
+
console.log(
|
|
309
|
+
chalk.grey(
|
|
310
|
+
`\n\t@${chalk.greenBright('warp-drive')}/${chalk.magentaBright(
|
|
311
|
+
'holodeck'
|
|
312
|
+
)} 🌅\n\t=================================\n`
|
|
313
|
+
) +
|
|
314
|
+
chalk.grey(
|
|
315
|
+
`\n\tHolodeck Access Granted\n\t\tprogram: ${chalk.magenta(name)}\n\t\tsettings: ${chalk.green(JSON.stringify(config).split('\n').join(' '))}\n\t\tdirectory: ${chalk.cyan(projectRoot)}\n\t\tengine: ${chalk.cyan(
|
|
316
|
+
isBun ? 'bun@' + Bun.version : 'node'
|
|
317
|
+
)}`
|
|
318
|
+
)
|
|
319
|
+
);
|
|
320
|
+
console.log(chalk.grey(`\n\tStarting Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
|
|
321
|
+
|
|
322
|
+
if (isBun) {
|
|
323
|
+
const serverProcess = Bun.spawn(
|
|
324
|
+
['node', '--experimental-default-type=module', CURRENT_FILE, JSON.stringify(options)],
|
|
325
|
+
{
|
|
326
|
+
env: process.env,
|
|
327
|
+
cwd: process.cwd(),
|
|
328
|
+
stdout: 'inherit',
|
|
329
|
+
stderr: 'inherit',
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
servers.set(projectRoot, serverProcess);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (servers.has(projectRoot)) {
|
|
337
|
+
throw new Error(`Holodeck is already running for project '${name}' at '${projectRoot}'`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
servers.set(projectRoot, createServer(options));
|
|
341
|
+
},
|
|
342
|
+
async endProgram() {
|
|
343
|
+
console.log(chalk.grey(`\n\tEnding Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
|
|
344
|
+
const projectRoot = process.cwd();
|
|
345
|
+
const name = await import(path.join(projectRoot, 'package.json'), { with: { type: 'json' } }).then(
|
|
346
|
+
(pkg) => pkg.name
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
if (!servers.has(projectRoot)) {
|
|
350
|
+
throw new Error(`Holodeck was not running for project '${name}' at '${projectRoot}'`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (isBun) {
|
|
354
|
+
const serverProcess = servers.get(projectRoot);
|
|
355
|
+
serverProcess.kill();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
servers.get(projectRoot).close();
|
|
360
|
+
servers.delete(projectRoot);
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
function main() {
|
|
365
|
+
const args = process.argv.slice();
|
|
366
|
+
if (!isBun && args.length) {
|
|
367
|
+
if (args[1] !== CURRENT_FILE) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const options = JSON.parse(args[2]);
|
|
371
|
+
createServer(options);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
main();
|
package/bin/cmd/_start.js
DELETED
package/bin/cmd/_stop.js
DELETED
package/bin/cmd/pm2.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/* global Bun, globalThis */
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import pm2 from 'pm2';
|
|
5
|
-
|
|
6
|
-
const { process } = globalThis;
|
|
7
|
-
|
|
8
|
-
export default async function pm2Delegate(cmd, _args) {
|
|
9
|
-
const pkg = JSON.parse(fs.readFileSync('./package.json'), 'utf8');
|
|
10
|
-
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
pm2.connect((err) => {
|
|
13
|
-
if (err) {
|
|
14
|
-
console.log('not able to connect to pm2');
|
|
15
|
-
console.error(err);
|
|
16
|
-
process.exit(2);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const options = {
|
|
20
|
-
script: './holodeck.mjs',
|
|
21
|
-
name: pkg.name + '::holodeck',
|
|
22
|
-
cwd: process.cwd(),
|
|
23
|
-
args: cmd === 'start' ? '-f' : '',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
pm2[cmd](cmd === 'start' ? options : options.name, (err, apps) => {
|
|
27
|
-
pm2.disconnect(); // Disconnects from PM2
|
|
28
|
-
if (err) {
|
|
29
|
-
console.log(`not able to ${cmd} pm2 for ${options.name}`);
|
|
30
|
-
console.error(err);
|
|
31
|
-
reject(err);
|
|
32
|
-
} else {
|
|
33
|
-
console.log(`pm2 ${cmd} successful for ${options.name}`);
|
|
34
|
-
resolve();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
}
|
package/bin/cmd/run.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/* global Bun, globalThis */
|
|
3
|
-
const isBun = typeof Bun !== 'undefined';
|
|
4
|
-
const { process } = globalThis;
|
|
5
|
-
import { spawn } from './spawn.js';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
|
|
8
|
-
export default async function run(args) {
|
|
9
|
-
const pkg = JSON.parse(fs.readFileSync('./package.json'), 'utf8');
|
|
10
|
-
const cmd = args[0];
|
|
11
|
-
const isPkgScript = pkg.scripts[cmd];
|
|
12
|
-
|
|
13
|
-
if (isBun) {
|
|
14
|
-
await spawn(['bun', 'run', 'holodeck:start-program']);
|
|
15
|
-
|
|
16
|
-
let exitCode = 0;
|
|
17
|
-
try {
|
|
18
|
-
await spawn(['bun', 'run', ...args]);
|
|
19
|
-
} catch (e) {
|
|
20
|
-
exitCode = e;
|
|
21
|
-
}
|
|
22
|
-
await spawn(['bun', 'run', 'holodeck:end-program']);
|
|
23
|
-
if (exitCode !== 0) {
|
|
24
|
-
process.exit(exitCode);
|
|
25
|
-
}
|
|
26
|
-
return;
|
|
27
|
-
} else {
|
|
28
|
-
await spawn(['pnpm', 'run', 'holodeck:start-program']);
|
|
29
|
-
|
|
30
|
-
let exitCode = 0;
|
|
31
|
-
try {
|
|
32
|
-
if (isPkgScript) {
|
|
33
|
-
const cmdArgs = pkg.scripts[cmd].split(' ');
|
|
34
|
-
if (args.length > 1) {
|
|
35
|
-
cmdArgs.push(...args.slice(1));
|
|
36
|
-
}
|
|
37
|
-
console.log({ cmdArgs });
|
|
38
|
-
await spawn(cmdArgs);
|
|
39
|
-
} else {
|
|
40
|
-
await spawn(['pnpm', 'exec', ...args]);
|
|
41
|
-
}
|
|
42
|
-
} catch (e) {
|
|
43
|
-
exitCode = e;
|
|
44
|
-
}
|
|
45
|
-
await spawn(['pnpm', 'run', 'holodeck:end-program']);
|
|
46
|
-
if (exitCode !== 0) {
|
|
47
|
-
process.exit(exitCode);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
package/bin/cmd/spawn.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/* global Bun, globalThis */
|
|
3
|
-
const isBun = typeof Bun !== 'undefined';
|
|
4
|
-
|
|
5
|
-
export async function spawn(args, options) {
|
|
6
|
-
if (isBun) {
|
|
7
|
-
const proc = Bun.spawn(args, {
|
|
8
|
-
env: process.env,
|
|
9
|
-
cwd: process.cwd(),
|
|
10
|
-
stdout: 'inherit',
|
|
11
|
-
stderr: 'inherit',
|
|
12
|
-
});
|
|
13
|
-
await proc.exited;
|
|
14
|
-
if (proc.exitCode !== 0) {
|
|
15
|
-
throw proc.exitCode;
|
|
16
|
-
}
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const { spawn } = await import('node:child_process');
|
|
21
|
-
|
|
22
|
-
// eslint-disable-next-line no-inner-declarations
|
|
23
|
-
function pSpawn(cmd, args, opts) {
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
const proc = spawn(cmd, args, opts);
|
|
26
|
-
proc.on('exit', (code) => {
|
|
27
|
-
if (code === 0) {
|
|
28
|
-
resolve();
|
|
29
|
-
} else {
|
|
30
|
-
reject(code);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
await pSpawn(args.shift(), args, {
|
|
37
|
-
stdio: 'inherit',
|
|
38
|
-
shell: true,
|
|
39
|
-
});
|
|
40
|
-
}
|
package/bin/holodeck.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
#!/bin/sh -
|
|
2
|
-
':'; /*-
|
|
3
|
-
test1=$(bun --version 2>&1) && exec bun "$0" "$@"
|
|
4
|
-
test2=$(node --version 2>&1) && exec node "$0" "$@"
|
|
5
|
-
exec printf '%s\n' "$test1" "$test2" 1>&2
|
|
6
|
-
*/
|
|
7
|
-
/* eslint-disable no-console */
|
|
8
|
-
/* global Bun, globalThis */
|
|
9
|
-
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
|
|
12
|
-
import { spawn } from './cmd/spawn.js';
|
|
13
|
-
|
|
14
|
-
const isBun = typeof Bun !== 'undefined';
|
|
15
|
-
const { process } = globalThis;
|
|
16
|
-
|
|
17
|
-
const args = isBun ? Bun.argv.slice(2) : process.argv.slice(2);
|
|
18
|
-
const command = args.shift();
|
|
19
|
-
|
|
20
|
-
const BUN_SUPPORTS_PM2 = false;
|
|
21
|
-
const BUN_SUPPORTS_HTTP2 = false;
|
|
22
|
-
|
|
23
|
-
if (command === 'run') {
|
|
24
|
-
console.log(
|
|
25
|
-
chalk.grey(
|
|
26
|
-
`\n\t@${chalk.greenBright('warp-drive')}/${chalk.magentaBright(
|
|
27
|
-
'holodeck'
|
|
28
|
-
)} 🌅\n\t=================================}\n`
|
|
29
|
-
) +
|
|
30
|
-
chalk.grey(
|
|
31
|
-
`\n\tHolodeck Access Granted\n\t\tprogram: ${chalk.green(args.join(' '))}\n\t\tengine: ${chalk.cyan(
|
|
32
|
-
isBun ? 'bun@' + Bun.version : 'node'
|
|
33
|
-
)}`
|
|
34
|
-
)
|
|
35
|
-
);
|
|
36
|
-
const run = await import('./cmd/run.js');
|
|
37
|
-
await run.default(args);
|
|
38
|
-
} else if (command === 'start') {
|
|
39
|
-
console.log(chalk.grey(`\n\tStarting Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
|
|
40
|
-
|
|
41
|
-
if (!isBun || (BUN_SUPPORTS_HTTP2 && BUN_SUPPORTS_PM2)) {
|
|
42
|
-
const pm2 = await import('./cmd/pm2.js');
|
|
43
|
-
await pm2.default('start', args);
|
|
44
|
-
} else {
|
|
45
|
-
console.log(`Downgrading to node to run pm2 due lack of http/2 or pm2 support in Bun`);
|
|
46
|
-
const __dirname = import.meta.dir;
|
|
47
|
-
const programPath = __dirname + '/cmd/_start.js';
|
|
48
|
-
await spawn(['node', programPath, ...args]);
|
|
49
|
-
}
|
|
50
|
-
} else if (command === 'end') {
|
|
51
|
-
console.log(chalk.grey(`\n\tEnding Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`));
|
|
52
|
-
|
|
53
|
-
if (!isBun || (BUN_SUPPORTS_HTTP2 && BUN_SUPPORTS_PM2)) {
|
|
54
|
-
const pm2 = await import('./cmd/pm2.js');
|
|
55
|
-
await pm2.default('stop', args);
|
|
56
|
-
} else {
|
|
57
|
-
console.log(`Downgrading to node to run pm2 due lack of http/2 or pm2 support in Bun`);
|
|
58
|
-
const __dirname = import.meta.dir;
|
|
59
|
-
const programPath = __dirname + '/cmd/_stop.js';
|
|
60
|
-
await spawn(['node', programPath, ...args]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
console.log(`\n\t${chalk.grey('The Computer has ended the program')}\n`);
|
|
64
|
-
}
|