@browserless.io/browserless 2.18.0 → 2.20.0-beta-1

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 (46) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/README.md +2 -2
  3. package/bin/browserless.js +2 -2
  4. package/build/browserless.js +4 -3
  5. package/build/browsers/index.js +6 -2
  6. package/build/config.d.ts +1 -1
  7. package/build/config.js +2 -2
  8. package/build/limiter.spec.js +1 -0
  9. package/build/routes/chrome/http/content.post.body.json +8 -8
  10. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  11. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  12. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  13. package/build/routes/chrome/tests/content.spec.js +1 -0
  14. package/build/routes/chrome/ws/page.d.ts +1 -0
  15. package/build/routes/chrome/ws/page.js +1 -0
  16. package/build/routes/chromium/http/content.post.body.json +8 -8
  17. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  18. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  19. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  20. package/build/sdk-utils.js +18 -46
  21. package/build/server.js +3 -1
  22. package/build/shared/json-version.http.js +1 -0
  23. package/build/shared/utils/performance/main.js +2 -1
  24. package/build/utils.d.ts +8 -0
  25. package/build/utils.js +15 -0
  26. package/docker/chrome/Dockerfile +3 -2
  27. package/docker/chromium/Dockerfile +3 -2
  28. package/docker/firefox/Dockerfile +3 -2
  29. package/docker/multi/Dockerfile +4 -2
  30. package/docker/webkit/Dockerfile +3 -2
  31. package/package.json +20 -30
  32. package/src/browserless.ts +5 -4
  33. package/src/browsers/index.ts +6 -2
  34. package/src/config.ts +2 -2
  35. package/src/limiter.spec.ts +1 -0
  36. package/src/routes/chrome/tests/content.spec.ts +1 -0
  37. package/src/routes/chrome/ws/page.ts +1 -0
  38. package/src/sdk-utils.ts +32 -60
  39. package/src/server.ts +4 -1
  40. package/src/shared/json-version.http.ts +1 -0
  41. package/src/shared/utils/performance/main.ts +1 -1
  42. package/src/utils.ts +17 -0
  43. package/static/docs/swagger.json +10 -10
  44. package/static/docs/swagger.min.json +9 -9
  45. package/static/function/client.js +14 -8
  46. package/static/function/index.html +14 -8
@@ -2,7 +2,15 @@ import { createInterface } from 'readline';
2
2
  import debug from 'debug';
3
3
  import fs from 'fs/promises';
4
4
  import path from 'path';
5
- import { spawn } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import { exec } from 'child_process';
7
+ const execAsync = promisify(exec);
8
+ const waitForCommand = async (cmd, workingDirectory) => new Promise((resolve, reject) => execAsync(cmd, { cwd: workingDirectory }).then(({ stderr }) => {
9
+ if (stderr) {
10
+ return reject(`Error running ${cmd}. See output for more details: \n${stderr}`);
11
+ }
12
+ return resolve();
13
+ }));
6
14
  export const getArgSwitches = () => {
7
15
  return process.argv.reduce((accum, arg, idx) => {
8
16
  if (!arg.startsWith('--')) {
@@ -28,7 +36,11 @@ export const getSourceFiles = async (cwd) => {
28
36
  const buildDir = path.join(cwd, 'build');
29
37
  const files = await fs.readdir(buildDir, { recursive: true });
30
38
  const [httpRoutes, webSocketRoutes] = files.reduce(([httpRoutes, webSocketRoutes], file) => {
39
+ const isInRootDir = !file.includes(path.sep);
31
40
  const parsed = path.parse(file);
41
+ if (isInRootDir) {
42
+ return [httpRoutes, webSocketRoutes];
43
+ }
32
44
  if (parsed.name.endsWith('http')) {
33
45
  httpRoutes.push(path.join(buildDir, file));
34
46
  }
@@ -58,50 +70,10 @@ export const prompt = async (question) => {
58
70
  });
59
71
  });
60
72
  };
73
+ // Exceptions are not caught, since any error would result in a crash regardless
61
74
  export const installDependencies = async (workingDirectory) => {
62
- await new Promise((resolve, reject) => {
63
- spawn('npm', ['i'], {
64
- cwd: workingDirectory,
65
- stdio: 'inherit',
66
- }).once('close', (code) => {
67
- if (code === 0) {
68
- return resolve();
69
- }
70
- return reject(`Error when installing dependencies, see output for more details`);
71
- });
72
- });
73
- await new Promise((resolve, reject) => {
74
- spawn('npx', 'playwright-core install --with-deps chromium firefox webkit'.split(' '), {
75
- cwd: workingDirectory,
76
- stdio: 'inherit',
77
- }).once('close', (code) => {
78
- if (code === 0) {
79
- return resolve();
80
- }
81
- return reject(`Error when installing dependencies, see output for more details`);
82
- });
83
- });
75
+ await waitForCommand('npm install', workingDirectory);
76
+ await waitForCommand('npx playwright-core install --with-deps chromium firefox webkit', workingDirectory);
84
77
  };
85
- export const buildDockerImage = async (cmd, projectDir) => new Promise((resolve, reject) => {
86
- const [docker, ...args] = cmd.split(' ');
87
- spawn(docker, args, {
88
- cwd: projectDir,
89
- stdio: 'inherit',
90
- }).once('close', (code) => {
91
- if (code === 0) {
92
- return resolve();
93
- }
94
- return reject(`Error when building Docker image, see output for more details`);
95
- });
96
- });
97
- export const buildTypeScript = async (buildDir, projectDir) => new Promise((resolve, reject) => {
98
- spawn('npx', ['tsc', '--outDir', buildDir], {
99
- cwd: projectDir,
100
- stdio: 'inherit',
101
- }).once('close', (code) => {
102
- if (code === 0) {
103
- return resolve();
104
- }
105
- return reject(`Error in building TypeScript, see output for more details`);
106
- });
107
- });
78
+ export const buildDockerImage = async (cmd, projectDir) => waitForCommand(cmd, projectDir);
79
+ export const buildTypeScript = async (buildDir, projectDir) => waitForCommand(`npx tsc --outDir ${buildDir}`, projectDir);
package/build/server.js CHANGED
@@ -241,7 +241,9 @@ export class HTTPServer extends EventEmitter {
241
241
  async shutdown() {
242
242
  this.logger.info(`HTTP Server is shutting down`);
243
243
  await new Promise((r) => this.server.close(r));
244
- this.server && this.server.removeAllListeners();
244
+ if (this.server) {
245
+ this.server.removeAllListeners();
246
+ }
245
247
  // @ts-ignore garbage collect this reference
246
248
  this.server = null;
247
249
  this.logger.info(`HTTP Server shutdown complete`);
@@ -25,6 +25,7 @@ export default class ChromiumJSONVersionGetRoute extends HTTPRoute {
25
25
  return jsonResponse(res, 200, this.cachedJSON);
26
26
  }
27
27
  catch (err) {
28
+ logger.warn(`Error handling request`, err);
28
29
  return writeResponse(res, 500, 'There was an error handling your request', contentTypes.text);
29
30
  }
30
31
  }
@@ -20,7 +20,8 @@ export default async ({ browser, context, logger, timeout, }) => {
20
20
  return;
21
21
  if (pid)
22
22
  process.kill(pid, 'SIGINT');
23
- timeoutId && clearTimeout(timeoutId);
23
+ if (timeoutId)
24
+ clearTimeout(timeoutId);
24
25
  closed = true;
25
26
  timeoutId = null;
26
27
  };
package/build/utils.d.ts CHANGED
@@ -17,7 +17,15 @@ export declare const buildDir: string;
17
17
  export declare const tsExtension = ".d.ts";
18
18
  export declare const jsonExtension = ".json";
19
19
  export declare const jsExtension = ".js";
20
+ export declare const isWin: boolean;
20
21
  export declare const id: () => string;
22
+ /**
23
+ * Normalizes a full-path by adding the `file://` protocol if needed.
24
+ *
25
+ * @param filepath - The file path to normalize.
26
+ * @returns The normalized file path.
27
+ */
28
+ export declare const normalizeFileProtocol: (filepath: string) => string;
21
29
  /**
22
30
  * Generates a random, Chromium-compliant page ID with "BLESS"
23
31
  * prepended. This prepended text signals to other parts of the
package/build/utils.js CHANGED
@@ -36,7 +36,22 @@ export const buildDir = path.join(path.resolve(), 'build');
36
36
  export const tsExtension = '.d.ts';
37
37
  export const jsonExtension = '.json';
38
38
  export const jsExtension = '.js';
39
+ export const isWin = process.platform === 'win32';
39
40
  export const id = () => crypto.randomUUID();
41
+ /**
42
+ * Normalizes a full-path by adding the `file://` protocol if needed.
43
+ *
44
+ * @param filepath - The file path to normalize.
45
+ * @returns The normalized file path.
46
+ */
47
+ export const normalizeFileProtocol = (filepath) => {
48
+ if (isWin) {
49
+ if (filepath.startsWith('file:///'))
50
+ return filepath;
51
+ return 'file:///' + filepath;
52
+ }
53
+ return filepath;
54
+ };
40
55
  /**
41
56
  * Generates a random, Chromium-compliant page ID with "BLESS"
42
57
  * prepended. This prepended text signals to other parts of the
@@ -26,8 +26,9 @@ RUN echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula sele
26
26
  fonts-ubuntu \
27
27
  fonts-wqy-zenhei
28
28
 
29
- RUN npx --yes playwright install chrome &&\
30
- npx --yes playwright install-deps chrome &&\
29
+ # NOTE it's important to not use npx playwright-core here since it'll likely install
30
+ # a more recent version than we potentially have in our own package.json
31
+ RUN ./node_modules/playwright-core/cli.js install --with-deps chrome &&\
31
32
  npm run build &&\
32
33
  npm run build:function &&\
33
34
  npm prune production &&\
@@ -26,8 +26,9 @@ RUN echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula sele
26
26
  fonts-ubuntu \
27
27
  fonts-wqy-zenhei
28
28
 
29
- RUN npx --yes playwright install chromium &&\
30
- npx --yes playwright install-deps chromium &&\
29
+ # NOTE it's important to not use npx playwright-core here since it'll likely install
30
+ # a more recent version than we potentially have in our own package.json
31
+ RUN ./node_modules/playwright-core/cli.js install --with-deps chromium &&\
31
32
  npm run build &&\
32
33
  npm run build:function &&\
33
34
  npm prune production &&\
@@ -26,8 +26,9 @@ RUN echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula sele
26
26
  fonts-ubuntu \
27
27
  fonts-wqy-zenhei
28
28
 
29
- RUN npx --yes playwright install firefox &&\
30
- npx --yes playwright install-deps firefox &&\
29
+ # NOTE it's important to not use npx playwright-core here since it'll likely install
30
+ # a more recent version than we potentially have in our own package.json
31
+ RUN ./node_modules/playwright-core/cli.js install --with-deps firefox &&\
31
32
  npm run build &&\
32
33
  npm prune production &&\
33
34
  chown -R blessuser:blessuser $APP_DIR &&\
@@ -29,12 +29,14 @@ RUN echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula sele
29
29
  # Chrome stable is only supported on non-ARM builds
30
30
  # so it's installation is conditional on whether or not amd64
31
31
  RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
32
- ./node_modules/.bin/playwright-core install --with-deps chrome; \
32
+ ./node_modules/playwright-core/cli.js install --with-deps chrome; \
33
33
  else \
34
34
  rm -rf ./src/routes/chrome; \
35
35
  fi
36
36
 
37
- RUN npx --yes playwright install --with-deps chromium firefox webkit &&\
37
+ # NOTE it's important to not use npx playwright-core here since it'll likely install
38
+ # a more recent version than we potentially have in our own package.json
39
+ RUN ./node_modules/playwright-core/cli.js install --with-deps chromium firefox webkit &&\
38
40
  npm run build &&\
39
41
  npm run build:function &&\
40
42
  npm prune production &&\
@@ -27,8 +27,9 @@ RUN echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula sele
27
27
  fonts-ubuntu \
28
28
  fonts-wqy-zenhei
29
29
 
30
- RUN npx --yes playwright install webkit &&\
31
- npx --yes playwright install-deps webkit &&\
30
+ # NOTE it's important to not use npx playwright-core here since it'll likely install
31
+ # a more recent version than we potentially have in our own package.json
32
+ RUN ./node_modules/playwright-core/cli.js install --with-deps webkit &&\
32
33
  npm run build &&\
33
34
  npm prune production &&\
34
35
  chown -R blessuser:blessuser $APP_DIR &&\
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserless.io/browserless",
3
- "version": "2.18.0",
3
+ "version": "2.20.0-beta-1",
4
4
  "license": "SSPL",
5
5
  "description": "The browserless platform",
6
6
  "author": "browserless.io",
@@ -25,9 +25,9 @@
25
25
  "dev": "npm run build:dev && env-cmd -f .env node build",
26
26
  "install:adblock": "node scripts/install-adblock.js",
27
27
  "install:debugger": "node scripts/install-debugger.js",
28
- "install:browsers": "npx --yes playwright install chromium firefox webkit chrome",
28
+ "install:browsers": "npx --no playwright-core install chromium firefox webkit",
29
29
  "install:dev": "npm run install:browsers && npm run install:debugger",
30
- "lint": "eslint . --ext .ts --fix",
30
+ "lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --ext .ts --fix",
31
31
  "prepack": "npm run build:dev",
32
32
  "prettier": "prettier '{src,functions,scripts,bin,external,.github}/**/*.{js,ts,json,yml,yaml,md}' --log-level error --write",
33
33
  "test": "cross-env DEBUG=quiet mocha",
@@ -48,21 +48,21 @@
48
48
  "tsconfig.json"
49
49
  ],
50
50
  "dependencies": {
51
- "debug": "^4.3.6",
51
+ "debug": "^4.3.7",
52
52
  "del": "^7.0.0",
53
53
  "enjoi": "^9.0.1",
54
- "file-type": "^19.4.1",
54
+ "file-type": "^19.5.0",
55
55
  "get-port": "^7.1.0",
56
56
  "gradient-string": "^2.0.0",
57
57
  "http-proxy": "^1.18.1",
58
- "lighthouse": "^12.2.0",
59
- "micromatch": "^4.0.7",
58
+ "lighthouse": "^12.2.1",
59
+ "micromatch": "^4.0.8",
60
60
  "playwright-1.41": "npm:playwright-core@1.41.2",
61
61
  "playwright-1.42": "npm:playwright-core@1.42.1",
62
62
  "playwright-1.43": "npm:playwright-core@1.43.1",
63
63
  "playwright-1.44": "npm:playwright-core@1.44.1",
64
- "playwright-core": "^1.46.1",
65
- "puppeteer-core": "^23.1.1",
64
+ "playwright-core": "^1.47.0",
65
+ "puppeteer-core": "^23.3.0",
66
66
  "puppeteer-extra": "^3.3.6",
67
67
  "puppeteer-extra-plugin-stealth": "^2.11.2",
68
68
  "queue": "^7.0.0",
@@ -70,33 +70,32 @@
70
70
  "tar-fs": "^3.0.6"
71
71
  },
72
72
  "optionalDependencies": {
73
- "@types/chai": "^4.3.17",
73
+ "@types/chai": "^4.3.18",
74
74
  "@types/debug": "^4.1.12",
75
75
  "@types/gradient-string": "^1.1.6",
76
76
  "@types/http-proxy": "^1.17.15",
77
77
  "@types/micromatch": "^4.0.9",
78
78
  "@types/mocha": "^10.0.7",
79
- "@types/node": "^22.5.0",
79
+ "@types/node": "^22.5.4",
80
80
  "@types/sinon": "^17.0.3",
81
- "@typescript-eslint/eslint-plugin": "^7.18.0",
82
- "@typescript-eslint/parser": "^7.18.0",
81
+ "@typescript-eslint/eslint-plugin": "^8.5.0",
82
+ "@typescript-eslint/parser": "^8.5.0",
83
83
  "assert": "^2.0.0",
84
84
  "chai": "^5.1.1",
85
85
  "cross-env": "^7.0.3",
86
86
  "env-cmd": "^10.1.0",
87
87
  "esbuild": "^0.23.1",
88
88
  "esbuild-plugin-polyfill-node": "^0.3.0",
89
- "eslint": "^8.57.0",
90
- "eslint-plugin-typescript-sort-keys": "^3.2.0",
89
+ "eslint": "^9.10.0",
91
90
  "extract-zip": "^2.0.1",
92
91
  "gunzip-maybe": "^1.4.2",
93
- "marked": "^14.0.0",
92
+ "marked": "^14.1.2",
94
93
  "mocha": "^10.7.3",
95
94
  "move-file": "^3.1.0",
96
95
  "prettier": "^3.3.3",
97
96
  "sinon": "^18.0.0",
98
97
  "ts-node": "^10.9.2",
99
- "typescript": "^5.5.4",
98
+ "typescript": "^5.6.2",
100
99
  "typescript-json-schema": "^0.65.1"
101
100
  },
102
101
  "playwrightVersions": {
@@ -111,32 +110,23 @@
111
110
  "root": true,
112
111
  "parser": "@typescript-eslint/parser",
113
112
  "plugins": [
114
- "@typescript-eslint",
115
- "typescript-sort-keys"
113
+ "@typescript-eslint"
116
114
  ],
117
115
  "extends": [
118
116
  "eslint:recommended",
119
117
  "plugin:@typescript-eslint/eslint-recommended",
120
- "plugin:@typescript-eslint/recommended",
121
- "plugin:typescript-sort-keys/recommended"
118
+ "plugin:@typescript-eslint/recommended"
122
119
  ],
123
120
  "ignorePatterns": [
124
121
  "node_modules/*",
122
+ "static/*",
123
+ "**.spec.ts",
125
124
  "build/*",
126
125
  ".DS_Store",
127
126
  ".no-git/*",
128
127
  "*.log"
129
128
  ],
130
129
  "rules": {
131
- "sort-keys": [
132
- "error",
133
- "asc",
134
- {
135
- "caseSensitive": true,
136
- "natural": false,
137
- "minKeys": 2
138
- }
139
- ],
140
130
  "semi": [
141
131
  2,
142
132
  "always"
@@ -25,8 +25,10 @@ import {
25
25
  WebKitPlaywright,
26
26
  WebSocketRoute,
27
27
  availableBrowsers,
28
+ dedent,
28
29
  getRouteFiles,
29
30
  makeExternalURL,
31
+ normalizeFileProtocol,
30
32
  printLogo,
31
33
  safeParse,
32
34
  } from '@browserless.io/browserless';
@@ -313,9 +315,7 @@ export class Browserless extends EventEmitter {
313
315
  }),
314
316
  );
315
317
 
316
- const wsImport = `${
317
- this.config.getIsWin() ? 'file:///' : ''
318
- }${wsRoute}`;
318
+ const wsImport = normalizeFileProtocol(wsRoute);
319
319
  const {
320
320
  default: Route,
321
321
  }: {
@@ -357,7 +357,8 @@ export class Browserless extends EventEmitter {
357
357
  !installedBrowsers.some((b) => b.name === route.browser?.name)
358
358
  ) {
359
359
  throw new Error(
360
- `Couldn't load route "${route.path}" due to missing browser binary for "${route.browser?.name}"`,
360
+ dedent(`Couldn't load route "${route.path}" due to missing browser binary for "${route.browser?.name}".
361
+ Installed Browsers: ${installedBrowsers.join(', ')}`),
361
362
  );
362
363
  }
363
364
  });
@@ -255,12 +255,16 @@ export class BrowserManager {
255
255
  },
256
256
  });
257
257
  if (response.ok) {
258
+ const externalUrl = new URL(serverAddress);
259
+ const protocol = externalUrl.protocol === 'https:' ? 'wss' : 'ws';
258
260
  const body = await response.json();
259
261
  for (const page of body) {
262
+ const devtoolsFrontendUrl = `/devtools/inspector.html?${protocol}=${externalUrl.host}${externalUrl.pathname}/devtools/page/${page.id}`;
260
263
  sessions.push({
261
264
  ...sessions[0],
262
265
  ...page,
263
266
  browserWSEndpoint: wsEndpoint,
267
+ devtoolsFrontendUrl,
264
268
  });
265
269
  }
266
270
  }
@@ -299,8 +303,8 @@ export class BrowserManager {
299
303
  global.setTimeout(() => {
300
304
  const session = this.browsers.get(browser);
301
305
  if (session) {
302
- this.log.trace(`Timer hit for "${session.id}"`),
303
- this.close(browser, session);
306
+ this.log.trace(`Timer hit for "${session.id}"`);
307
+ this.close(browser, session);
304
308
  }
305
309
  }, timeout),
306
310
  );
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { exists, keyLength, untildify } from '@browserless.io/browserless';
1
+ import { exists, isWin as isWindows, keyLength, untildify } from '@browserless.io/browserless';
2
2
  import { EventEmitter } from 'events';
3
3
  import debug from 'debug';
4
4
  import { fileURLToPath } from 'url';
@@ -112,8 +112,8 @@ const getDebug = () => {
112
112
  export class Config extends EventEmitter {
113
113
  protected readonly debug = getDebug();
114
114
  protected readonly host = process.env.HOST ?? 'localhost';
115
+ protected readonly isWin = isWindows;
115
116
  protected external = process.env.PROXY_URL ?? process.env.EXTERNAL;
116
- protected readonly isWin = process.platform === 'win32';
117
117
 
118
118
  protected port = +(process.env.PORT ?? '3000');
119
119
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-unused-expressions */
1
2
  import {
2
3
  Config,
3
4
  Hooks,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-unused-expressions */
1
2
  import {
2
3
  Browserless,
3
4
  Config,
@@ -4,6 +4,7 @@ import { default as Page, QuerySchema } from '../../../shared/page.ws.js';
4
4
  export default class ChromePageWebSocketRoute extends Page {
5
5
  name = BrowserlessRoutes.ChromePageWebSocketRoute;
6
6
  browser = ChromeCDP;
7
+ auth = false;
7
8
  }
8
9
 
9
10
  export { QuerySchema };
package/src/sdk-utils.ts CHANGED
@@ -2,7 +2,24 @@ import { createInterface } from 'readline';
2
2
  import debug from 'debug';
3
3
  import fs from 'fs/promises';
4
4
  import path from 'path';
5
- import { spawn } from 'child_process';
5
+ import { promisify } from 'util';
6
+
7
+ import { exec } from 'child_process';
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ const waitForCommand = async (cmd: string, workingDirectory: string) =>
12
+ new Promise<void>((resolve, reject) =>
13
+ execAsync(cmd, { cwd: workingDirectory }).then(({ stderr }) => {
14
+ if (stderr) {
15
+ return reject(
16
+ `Error running ${cmd}. See output for more details: \n${stderr}`,
17
+ );
18
+ }
19
+
20
+ return resolve();
21
+ }),
22
+ );
6
23
 
7
24
  export const getArgSwitches = () => {
8
25
  return process.argv.reduce(
@@ -41,7 +58,13 @@ export const getSourceFiles = async (cwd: string) => {
41
58
  const files = await fs.readdir(buildDir, { recursive: true });
42
59
  const [httpRoutes, webSocketRoutes] = files.reduce(
43
60
  ([httpRoutes, webSocketRoutes], file) => {
61
+ const isInRootDir = !file.includes(path.sep);
44
62
  const parsed = path.parse(file);
63
+
64
+ if (isInRootDir) {
65
+ return [httpRoutes, webSocketRoutes];
66
+ }
67
+
45
68
  if (parsed.name.endsWith('http')) {
46
69
  httpRoutes.push(path.join(buildDir, file));
47
70
  }
@@ -81,74 +104,23 @@ export const prompt = async (question: string) => {
81
104
  });
82
105
  };
83
106
 
107
+ // Exceptions are not caught, since any error would result in a crash regardless
84
108
  export const installDependencies = async (
85
109
  workingDirectory: string,
86
110
  ): Promise<void> => {
87
- await new Promise<void>((resolve, reject) => {
88
- spawn('npm', ['i'], {
89
- cwd: workingDirectory,
90
- stdio: 'inherit',
91
- }).once('close', (code) => {
92
- if (code === 0) {
93
- return resolve();
94
- }
95
- return reject(
96
- `Error when installing dependencies, see output for more details`,
97
- );
98
- });
99
- });
100
- await new Promise<void>((resolve, reject) => {
101
- spawn(
102
- 'npx',
103
- 'playwright-core install --with-deps chromium firefox webkit'.split(' '),
104
- {
105
- cwd: workingDirectory,
106
- stdio: 'inherit',
107
- },
108
- ).once('close', (code) => {
109
- if (code === 0) {
110
- return resolve();
111
- }
112
- return reject(
113
- `Error when installing dependencies, see output for more details`,
114
- );
115
- });
116
- });
111
+ await waitForCommand('npm install', workingDirectory);
112
+ await waitForCommand(
113
+ 'npx playwright-core install --with-deps chromium firefox webkit',
114
+ workingDirectory,
115
+ );
117
116
  };
118
117
 
119
118
  export const buildDockerImage = async (
120
119
  cmd: string,
121
120
  projectDir: string,
122
- ): Promise<void> =>
123
- new Promise((resolve, reject) => {
124
- const [docker, ...args] = cmd.split(' ');
125
- spawn(docker, args, {
126
- cwd: projectDir,
127
- stdio: 'inherit',
128
- }).once('close', (code) => {
129
- if (code === 0) {
130
- return resolve();
131
- }
132
- return reject(
133
- `Error when building Docker image, see output for more details`,
134
- );
135
- });
136
- });
121
+ ): Promise<void> => waitForCommand(cmd, projectDir);
137
122
 
138
123
  export const buildTypeScript = async (
139
124
  buildDir: string,
140
125
  projectDir: string,
141
- ): Promise<void> =>
142
- new Promise((resolve, reject) => {
143
- spawn('npx', ['tsc', '--outDir', buildDir], {
144
- cwd: projectDir,
145
- stdio: 'inherit',
146
- }).once('close', (code) => {
147
- if (code === 0) {
148
- return resolve();
149
- }
150
- return reject(
151
- `Error in building TypeScript, see output for more details`,
152
- );
153
- });
154
- });
126
+ ): Promise<void> => waitForCommand(`npx tsc --outDir ${buildDir}`, projectDir);
package/src/server.ts CHANGED
@@ -415,7 +415,10 @@ export class HTTPServer extends EventEmitter {
415
415
  public async shutdown(): Promise<void> {
416
416
  this.logger.info(`HTTP Server is shutting down`);
417
417
  await new Promise((r) => this.server.close(r));
418
- this.server && this.server.removeAllListeners();
418
+
419
+ if (this.server) {
420
+ this.server.removeAllListeners();
421
+ }
419
422
 
420
423
  // @ts-ignore garbage collect this reference
421
424
  this.server = null;
@@ -45,6 +45,7 @@ export default class ChromiumJSONVersionGetRoute extends HTTPRoute {
45
45
  }
46
46
  return jsonResponse(res, 200, this.cachedJSON);
47
47
  } catch (err) {
48
+ logger.warn(`Error handling request`, err);
48
49
  return writeResponse(
49
50
  res,
50
51
  500,
@@ -38,7 +38,7 @@ export default async ({
38
38
  const close = (pid?: number) => {
39
39
  if (closed) return;
40
40
  if (pid) process.kill(pid, 'SIGINT');
41
- timeoutId && clearTimeout(timeoutId);
41
+ if (timeoutId) clearTimeout(timeoutId);
42
42
  closed = true;
43
43
  timeoutId = null;
44
44
  };
package/src/utils.ts CHANGED
@@ -64,9 +64,26 @@ export const buildDir: string = path.join(path.resolve(), 'build');
64
64
  export const tsExtension = '.d.ts';
65
65
  export const jsonExtension = '.json';
66
66
  export const jsExtension = '.js';
67
+ export const isWin = process.platform === 'win32';
67
68
 
68
69
  export const id = (): string => crypto.randomUUID();
69
70
 
71
+ /**
72
+ * Normalizes a full-path by adding the `file://` protocol if needed.
73
+ *
74
+ * @param filepath - The file path to normalize.
75
+ * @returns The normalized file path.
76
+ */
77
+ export const normalizeFileProtocol = (filepath: string) => {
78
+ if (isWin) {
79
+ if (filepath.startsWith('file:///')) return filepath;
80
+
81
+ return 'file:///' + filepath;
82
+ }
83
+
84
+ return filepath;
85
+ };
86
+
70
87
  /**
71
88
  * Generates a random, Chromium-compliant page ID with "BLESS"
72
89
  * prepended. This prepended text signals to other parts of the