diff --git a/README.md b/README.md
index f38cd28..4342f78 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,17 @@
With Node.js, Deno or Bun there are so many JavaScript environments to choose from. However, nothing is as good as the browser environment. `bx` gives you an execution runtime for the browser.
-With `bx` you can easily run this script within different browser environments:
+# Install
+
+No install needed, just run it directly via `npx`, e.g.:
+
+```sh
+npx bx "console.log(navigator.userAgent)"
+```
+
+# Usage
+
+With `bx` you can easily run scripts (JS or TS) within different browser environments:
```sh
> echo "console.log(navigator.userAgent)" &> script.js
@@ -33,7 +43,42 @@ Running this with `bx` results in:
Hello World!
```
-## Session Management
+# Run Programmatically
+
+You can also run `bx` programmatically, e.g. to hydrate components within the browser. For example, to hydrate a [Lit](https://lit.dev/) component through a [Koa](https://koajs.com/) server, you can run this script:
+
+```ts
+import path from 'node:path'
+import Koa from 'koa'
+
+import { run } from '../../dist/index.js'
+
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
+const app = new Koa()
+
+app.use(async (ctx) => {
+ if (ctx.path === '/favicon.ico') {
+ return
+ }
+
+ ctx.body = await run(/*js*/`
+ import {render} from '@lit-labs/ssr';
+ import {html} from 'lit';
+ import './component.ts';
+
+ const dom = await render(html\`\`);
+ export default Array.from(dom).join('\\n')
+ `, {
+ browserName: 'chrome',
+ rootDir: __dirname
+ })
+})
+
+app.listen(3000)
+console.log('Server running at http://localhost:3000/');
+```
+
+# Session Management
If you like to speed up your execution, you can create browser sessions on your system and run scripts through them immediately without having to spin up the browser. You can create a session via:
diff --git a/bin/bx b/bin/bx
index e5001c2..ba54d70 100755
--- a/bin/bx
+++ b/bin/bx
@@ -1,4 +1,4 @@
#!/usr/bin/env node
-import cli from '../dist/index.js'
+import cli from '../dist/cli.js'
cli()
\ No newline at end of file
diff --git a/examples/lit-hydrate/component.ts b/examples/lit-hydrate/component.ts
new file mode 100644
index 0000000..544116a
--- /dev/null
+++ b/examples/lit-hydrate/component.ts
@@ -0,0 +1,29 @@
+import { LitElement, css, html } from 'lit';
+
+export class SimpleGreeting extends LitElement {
+ name: string;
+
+ static properties = {
+ name: {},
+ };
+
+ // Define scoped styles right with your component, in plain CSS
+ static styles = css`
+ :host {
+ color: blue;
+ }
+ `;
+
+ constructor() {
+ super();
+ // Declare reactive properties
+ this.name = 'World';
+ }
+
+ // Render the UI as a function of component state
+ render() {
+ return html`
Hello, ${this.name}!
`;
+ }
+}
+
+customElements.define('simple-greeting', SimpleGreeting);
\ No newline at end of file
diff --git a/examples/lit-hydrate/example.ts b/examples/lit-hydrate/example.ts
new file mode 100644
index 0000000..e7e4301
--- /dev/null
+++ b/examples/lit-hydrate/example.ts
@@ -0,0 +1,30 @@
+import path from 'node:path'
+import Koa from 'koa'
+
+import { run } from '../../dist/index.js'
+
+import {render} from '@lit-labs/ssr'
+
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
+const app = new Koa()
+
+app.use(async (ctx) => {
+ if (ctx.path === '/favicon.ico') {
+ return
+ }
+
+ ctx.body = await run(/*js*/`
+ import {render} from '@lit-labs/ssr';
+ import {html} from 'lit';
+ import './component.ts';
+
+ const dom = await render(html\`\`);
+ export default Array.from(dom).join('\\n')
+ `, {
+ browserName: 'chrome',
+ rootDir: __dirname
+ })
+})
+
+app.listen(3000)
+console.log('Server running at http://localhost:3000/');
diff --git a/examples/lit-hydrate/package-lock.json b/examples/lit-hydrate/package-lock.json
new file mode 100644
index 0000000..08285cb
--- /dev/null
+++ b/examples/lit-hydrate/package-lock.json
@@ -0,0 +1,666 @@
+{
+ "name": "lit-hydration-example",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "lit-hydration-example",
+ "dependencies": {
+ "koa": "^2.15.0",
+ "lit": "^3.1.2"
+ },
+ "devDependencies": {
+ "@lit-labs/ssr": "^3.2.2"
+ }
+ },
+ "node_modules/@lit-labs/ssr": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-3.2.2.tgz",
+ "integrity": "sha512-He5TzeNPM9ECmVpgXRYmVlz0UA5YnzHlT43kyLi2Lu6mUidskqJVonk9W5K699+2DKhoXp8Ra4EJmHR6KrcW1Q==",
+ "dev": true,
+ "dependencies": {
+ "@lit-labs/ssr-client": "^1.1.7",
+ "@lit-labs/ssr-dom-shim": "^1.2.0",
+ "@lit/reactive-element": "^2.0.4",
+ "@parse5/tools": "^0.3.0",
+ "@types/node": "^16.0.0",
+ "enhanced-resolve": "^5.10.0",
+ "lit": "^3.1.2",
+ "lit-element": "^4.0.4",
+ "lit-html": "^3.1.2",
+ "node-fetch": "^3.2.8",
+ "parse5": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=13.9.0"
+ }
+ },
+ "node_modules/@lit-labs/ssr-client": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-client/-/ssr-client-1.1.7.tgz",
+ "integrity": "sha512-VvqhY/iif3FHrlhkzEPsuX/7h/NqnfxLwVf0p8ghNIlKegRyRqgeaJevZ57s/u/LiFyKgqksRP5n+LmNvpxN+A==",
+ "dev": true,
+ "dependencies": {
+ "@lit/reactive-element": "^2.0.4",
+ "lit": "^3.1.2",
+ "lit-html": "^3.1.2"
+ }
+ },
+ "node_modules/@lit-labs/ssr-dom-shim": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
+ "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
+ },
+ "node_modules/@lit/reactive-element": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
+ "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0"
+ }
+ },
+ "node_modules/@parse5/tools": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz",
+ "integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==",
+ "dev": true,
+ "dependencies": {
+ "parse5": "^7.0.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "16.18.83",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.83.tgz",
+ "integrity": "sha512-TmBqzDY/GeCEmLob/31SunOQnqYE3ZiiuEh1U9o3HqE1E2cqKZQA5RQg4krEguCY3StnkXyDmCny75qyFLx/rA==",
+ "dev": true
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cache-content-type": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
+ "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
+ "dependencies": {
+ "mime-types": "^2.1.18",
+ "ylru": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookies": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
+ "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "keygrip": "~1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw=="
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "dev": true,
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/http-assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
+ "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==",
+ "dependencies": {
+ "deep-equal": "~1.0.1",
+ "http-errors": "~1.8.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-errors/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/keygrip": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
+ "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
+ "dependencies": {
+ "tsscmp": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/koa": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.0.tgz",
+ "integrity": "sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==",
+ "dependencies": {
+ "accepts": "^1.3.5",
+ "cache-content-type": "^1.0.0",
+ "content-disposition": "~0.5.2",
+ "content-type": "^1.0.4",
+ "cookies": "~0.9.0",
+ "debug": "^4.3.2",
+ "delegates": "^1.0.0",
+ "depd": "^2.0.0",
+ "destroy": "^1.0.4",
+ "encodeurl": "^1.0.2",
+ "escape-html": "^1.0.3",
+ "fresh": "~0.5.2",
+ "http-assert": "^1.3.0",
+ "http-errors": "^1.6.3",
+ "is-generator-function": "^1.0.7",
+ "koa-compose": "^4.1.0",
+ "koa-convert": "^2.0.0",
+ "on-finished": "^2.3.0",
+ "only": "~0.0.2",
+ "parseurl": "^1.3.2",
+ "statuses": "^1.5.0",
+ "type-is": "^1.6.16",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4"
+ }
+ },
+ "node_modules/koa-compose": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
+ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
+ },
+ "node_modules/koa-convert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz",
+ "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==",
+ "dependencies": {
+ "co": "^4.6.0",
+ "koa-compose": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/lit": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz",
+ "integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==",
+ "dependencies": {
+ "@lit/reactive-element": "^2.0.4",
+ "lit-element": "^4.0.4",
+ "lit-html": "^3.1.2"
+ }
+ },
+ "node_modules/lit-element": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz",
+ "integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0",
+ "@lit/reactive-element": "^2.0.4",
+ "lit-html": "^3.1.2"
+ }
+ },
+ "node_modules/lit-html": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz",
+ "integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==",
+ "dependencies": {
+ "@types/trusted-types": "^2.0.2"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "dev": true,
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/only": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz",
+ "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ=="
+ },
+ "node_modules/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "dev": true,
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tsscmp": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
+ "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
+ "engines": {
+ "node": ">=0.6.x"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/ylru": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.3.2.tgz",
+ "integrity": "sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ }
+ }
+}
diff --git a/examples/lit-hydrate/package.json b/examples/lit-hydrate/package.json
new file mode 100644
index 0000000..97df737
--- /dev/null
+++ b/examples/lit-hydrate/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "lit-hydration-example",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "koa": "^2.15.0",
+ "lit": "^3.1.2"
+ },
+ "devDependencies": {
+ "@lit-labs/ssr": "^3.2.2"
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index ed82ec0..ec2a2e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"npm-run-all": "^4.1.5",
"rimraf": "^5.0.5",
"tinybench": "^2.6.0",
+ "tsx": "^4.7.1",
"typescript": "^5.3.3",
"vitest": "^1.2.2"
}
@@ -2554,6 +2555,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-tsconfig": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz",
+ "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==",
+ "dev": true,
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
"node_modules/get-uri": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz",
@@ -4417,6 +4430,15 @@
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="
},
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
"node_modules/responselike": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
@@ -5162,6 +5184,25 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
+ "node_modules/tsx": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz",
+ "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "~0.19.10",
+ "get-tsconfig": "^4.7.2"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
diff --git a/package.json b/package.json
index ac537c2..f25bcdf 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,9 @@
"build": "run-s clean compile",
"clean": "rimraf dist coverage",
"compile": "tsc",
+ "example:lit": "cd examples/lit-hydrate && npm install && tsx example.ts",
+ "example:script": "./bin/bx examples/consoleLog.js --browserName chrome",
+ "example:html": "./bin/bx examples/consoleLog.html --browserName chrome",
"test": "vitest --run",
"watch": "tsc -w"
},
@@ -34,6 +37,7 @@
"npm-run-all": "^4.1.5",
"rimraf": "^5.0.5",
"tinybench": "^2.6.0",
+ "tsx": "^4.7.1",
"typescript": "^5.3.3",
"vitest": "^1.2.2"
},
diff --git a/src/cli.ts b/src/cli.ts
new file mode 100644
index 0000000..f1f6f6a
--- /dev/null
+++ b/src/cli.ts
@@ -0,0 +1,18 @@
+import yargs from 'yargs'
+import { hideBin } from 'yargs/helpers'
+
+import commands from './cli/index.js'
+import { handler } from './cli/run.js'
+
+export default async function cli () {
+ const argv = yargs(hideBin(process.argv))
+ .command(commands)
+ .help()
+ .argv
+
+ const cmdNames = commands.map((command: { command: string }) => command.command.split(' ')[0])
+ const params = await argv
+ if (!cmdNames.includes(`${params['_'][0]}`)) {
+ await handler()
+ }
+}
diff --git a/src/cli/run.ts b/src/cli/run.ts
index 1cbe2cd..26b81c4 100644
--- a/src/cli/run.ts
+++ b/src/cli/run.ts
@@ -3,8 +3,9 @@ import { hideBin } from 'yargs/helpers'
import type { Argv } from 'yargs'
import { ViteServer } from '../server.js'
-import { run } from '../runner.js'
+import { run as runCall } from '../runner.js'
import { CLI_EPILOGUE } from '../constants.js'
+import type { RunnerArgs } from '../types.js'
export const command = ' [options]'
export const desc = 'Run script, html file or URL.'
@@ -45,10 +46,12 @@ export const builder = (yargs: Argv) => {
export const handler = async () => {
const params = await yargsInstance.parse()
- const rootDir = params.rootDir || process.cwd()
- const server = new ViteServer({ root: rootDir })
const target = params._[0] as string | undefined
+ await run(target, params)
+ process.exit(0)
+}
+export async function run (target?: string, params?: RunnerArgs) {
if (!target) {
console.error('Error: No target provided')
process.exit(1)
@@ -59,13 +62,18 @@ export const handler = async () => {
process.exit(1)
}
+ let server: ViteServer | undefined
+ let result: any
try {
+ const rootDir = params?.rootDir || process.cwd()
+ const server = new ViteServer({ root: rootDir })
const env = await server.start(target)
- await run(env, params)
+ result = await runCall(env, params || {})
} catch (err) {
console.error('Error:', (err as Error).message)
- process.exit(1)
} finally {
- await server.stop()
+ await server?.stop()
}
+
+ return result
}
\ No newline at end of file
diff --git a/src/cli/session.ts b/src/cli/session.ts
index c81b494..2d95dcf 100644
--- a/src/cli/session.ts
+++ b/src/cli/session.ts
@@ -5,7 +5,7 @@ import type { Argv } from 'yargs'
import { cmdArgs as runCmdArgs } from './run.js'
import { CLI_EPILOGUE } from '../constants.js'
import { initBrowserSession } from '../utils.js'
-import { SessionManager } from '../session.js'
+import { deleteSession, deleteAllSessions, listSessions, saveSession } from '../session.js'
export const command = 'session [options]'
export const desc = 'Manage `bx` sessions.'
@@ -42,12 +42,12 @@ export const handler = async () => {
const params = await yargsInstance.parse()
if (typeof params.kill === 'string') {
- await SessionManager.deleteSession(params.kill)
+ await deleteSession(params.kill)
return console.log(`Session "${params.kill}" stopped`)
}
if (params.killAll) {
- await SessionManager.deleteAllSessions()
+ await deleteAllSessions()
return console.log('All sessions stopped')
}
@@ -57,7 +57,7 @@ export const handler = async () => {
* if browserName is not provided, list all sessions
*/
if (!browserName) {
- const sessions = await SessionManager.listSessions()
+ const sessions = await listSessions()
if (sessions.length === 0) {
return console.log('No sessions found!')
}
@@ -74,7 +74,7 @@ export const handler = async () => {
* if no session name is provided, generate a random one
*/
if (!sessionName) {
- const sessions = await SessionManager.listSessions()
+ const sessions = await listSessions()
const browserNameSessions = sessions.filter((session) => session.requestedCapabilities.browserName === browserName)
sessionName = `${browserName}-${browserNameSessions.length}`
}
@@ -82,7 +82,7 @@ export const handler = async () => {
const headless = Boolean(params.headless)
const rootDir = params.rootDir || process.cwd()
const browser = await initBrowserSession({ ...params, rootDir, headless, browserName })
- await SessionManager.saveSession(browser, sessionName)
+ await saveSession(browser, sessionName)
console.log(`Session "${sessionName}" started, you can now run scripts faster e.g. \`npx bx ./script.js --sessionName ${sessionName}\``)
process.exit(0)
}
\ No newline at end of file
diff --git a/src/constants.ts b/src/constants.ts
index f8a1bd3..ade7409 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -2,19 +2,7 @@ import { createRequire } from 'node:module'
export const SUPPORTED_FILE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.html']
export const IS_CI = Boolean(process.env.HEADLESS || process.env.CI)
-export const CLI_OPTIONS = {
- browserName: { type: 'string', alias: 'b', default: 'chrome' },
- browserVersion: { type: 'string', alias: 'v' },
- headless: { type: 'boolean', alias: 'h', default: IS_CI },
- rootDir: { type: 'string', alias: 'r', default: process.cwd() },
- sessionName: { type: 'string', alias: 's' }
-} as const
-
-export const PARSE_OPTIONS = {
- options: CLI_OPTIONS,
- tokens: true,
- allowPositionals: true
-} as const
+export const DEFAULT_BROWSER = 'chrome'
const require = createRequire(import.meta.url)
export const pkg = require('../package.json')
diff --git a/src/index.ts b/src/index.ts
index f1f6f6a..7b22e33 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,18 +1,2 @@
-import yargs from 'yargs'
-import { hideBin } from 'yargs/helpers'
-
-import commands from './cli/index.js'
-import { handler } from './cli/run.js'
-
-export default async function cli () {
- const argv = yargs(hideBin(process.argv))
- .command(commands)
- .help()
- .argv
-
- const cmdNames = commands.map((command: { command: string }) => command.command.split(' ')[0])
- const params = await argv
- if (!cmdNames.includes(`${params['_'][0]}`)) {
- await handler()
- }
-}
+export * from './session.js'
+export { run } from './cli/run.js'
\ No newline at end of file
diff --git a/src/runner.ts b/src/runner.ts
index 602a7b1..e92834e 100644
--- a/src/runner.ts
+++ b/src/runner.ts
@@ -1,12 +1,12 @@
import { initBrowserSession } from './utils.js'
-import { SessionManager } from './session.js'
+import { loadSession } from './session.js'
import type { ExecutionEnvironment, RunnerArgs } from './types.js'
const SAFARI_ERROR_PREFIX = 'module code@'
export async function run (env: ExecutionEnvironment, args: RunnerArgs) {
const browser = args.sessionName
- ? await SessionManager.loadSession(args.sessionName)
+ ? await loadSession(args.sessionName)
: await initBrowserSession(args)
let error: Error | undefined
@@ -16,8 +16,8 @@ export async function run (env: ExecutionEnvironment, args: RunnerArgs) {
* at the same time
*/
browser.url(env.url)
- await new Promise((resolve) => {
- env.server.ws.on('bx:event', (message) => {
+ const result = await new Promise((resolve) => {
+ env.server.hot.on('bx:event', (message) => {
if (message.name === 'errorEvent') {
error = new Error(message.message)
@@ -30,10 +30,10 @@ export async function run (env: ExecutionEnvironment, args: RunnerArgs) {
}
error.stack = message.error.replace(`http://localhost:${env.server.config.server.port}/@fs`, 'file://')
- return resolve()
+ return resolve(undefined)
}
if (message.name === 'doneEvent') {
- return resolve()
+ return resolve(message.result)
}
})
})
@@ -46,7 +46,8 @@ export async function run (env: ExecutionEnvironment, args: RunnerArgs) {
}
if (error) {
- console.error(error);
- process.exit(1)
+ throw error
}
+
+ return result
}
\ No newline at end of file
diff --git a/src/server.ts b/src/server.ts
index 5ab785a..d6353d7 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -5,7 +5,8 @@ import { createServer, type InlineConfig, type ViteDevServer, type Plugin } from
import type { ExecutionEnvironment, ConsoleEvent } from './types.js'
const __dirname = path.dirname(new URL(import.meta.url).pathname)
-
+const virtualModuleId = 'virtual:inline'
+const resolvedVirtualModuleId = '\0' + virtualModuleId
const DEFAULT_OPTIONS: InlineConfig = {
configFile: false,
server: { host: 'localhost', port: 0 },
@@ -16,22 +17,22 @@ export class ViteServer {
#server?: ViteDevServer
#options: InlineConfig
- constructor (options: InlineConfig) {
+ constructor(options: InlineConfig) {
this.#options = {
...DEFAULT_OPTIONS,
...options,
}
}
- async start (filename: string): Promise {
- let onConnectHandler: (value: ViteDevServer) => void = () => {}
+ async start(target: string): Promise {
+ let onConnectHandler: (value: ViteDevServer) => void = () => { }
const connectPromise = new Promise((resolve) => {
onConnectHandler = resolve
})
this.#server = await createServer({
...this.#options,
- plugins: [await instrument(filename, onConnectHandler)]
+ plugins: [await instrument(target, onConnectHandler)]
})
await this.#server.listen()
return {
@@ -41,26 +42,38 @@ export class ViteServer {
}
}
- async stop () {
+ async stop() {
await this.#server?.close()
}
}
-async function instrument (filename: string, onConnect: (value: ViteDevServer) => void): Promise {
+async function instrument(target: string, onConnect: (value: ViteDevServer) => void): Promise {
const instrumentation = await fs.readFile(path.resolve(__dirname, 'browser', 'index.js'), 'utf-8')
const sendFinishEvent = `requestAnimationFrame(() => import.meta.hot?.send('bx:event', { name: 'doneEvent' }))`
return {
name: 'instrument',
enforce: 'pre',
+ resolveId(id) {
+ if (id === virtualModuleId) {
+ return resolvedVirtualModuleId
+ }
+ return null
+ },
+ load(id) {
+ if (id === resolvedVirtualModuleId) {
+ return target
+ }
+ return null
+ },
transform: (code, id) => {
- if (id === filename) {
+ if (id === target) {
return {
code: `${code}\n${sendFinishEvent}`
}
}
return null
},
- configureServer (server) {
+ configureServer(server) {
server.middlewares.use(async (req, res, next) => {
/**
* don't return test page when sourcemaps are requested
@@ -69,15 +82,23 @@ async function instrument (filename: string, onConnect: (value: ViteDevServer) =
return next()
}
- const code = path.extname(filename) === '.html'
- ? await fs.readFile(filename, 'utf-8')
- : ``
+ const code = target.startsWith('./') || target.startsWith('/')
+ ? path.extname(target) === '.html'
+ ? await fs.readFile(target, 'utf-8')
+ : ``
+ : ``
const template = `
${code}
- ${path.extname(filename) === '.html' ? `` : ''}
+ ${path.extname(target) === '.html' ? `` : ''}
`
res.end(await server.transformIndexHtml(`${req.originalUrl}`, template))
})
@@ -92,6 +113,6 @@ async function instrument (filename: string, onConnect: (value: ViteDevServer) =
}
}
-function handleConsole (message: ConsoleEvent) {
+function handleConsole(message: ConsoleEvent) {
console[message.type](...message.args)
}
diff --git a/src/session.ts b/src/session.ts
index 2d2228a..ec7ce0d 100644
--- a/src/session.ts
+++ b/src/session.ts
@@ -24,64 +24,62 @@ interface SessionFile {
* @description To allow run scripts faster, user can keep a browser session around.
* This class is responsible for managing the session.
*/
-export class SessionManager {
- static async loadSession(sessionName: string) {
- const sessionFilePath = getSessionFilePath(sessionName)
- const sessionExists = await fs.access(sessionFilePath).then(() => true, () => false)
- if (!sessionExists) {
- throw new Error(`Session "${sessionName}" not found`)
- }
-
- const session: SessionFile = JSON.parse(await fs.readFile(sessionFilePath, 'utf8'))
- return attach({ ...session.options, ...session })
- }
-
- static async saveSession(browser: WebdriverIO.Browser, sessionName: string) {
- const { capabilities, requestedCapabilities, sessionId, options } = browser
- await fs.writeFile(
- getSessionFilePath(sessionName),
- JSON.stringify({ name: sessionName, capabilities, requestedCapabilities, sessionId, options } as SessionFile)
- )
+export async function loadSession(sessionName: string) {
+ const sessionFilePath = getSessionFilePath(sessionName)
+ const sessionExists = await fs.access(sessionFilePath).then(() => true, () => false)
+ if (!sessionExists) {
+ throw new Error(`Session "${sessionName}" not found`)
}
- static async listSessions(): Promise {
- const files = await fs.readdir(SESSION_DIR)
- const sessionFiles: SessionFile[] = []
+ const session: SessionFile = JSON.parse(await fs.readFile(sessionFilePath, 'utf8'))
+ return attach({ ...session.options, ...session })
+}
- for (const file of files) {
- if (file.startsWith(SESSION_FILE_PREFIX)) {
- const session = JSON.parse(await fs.readFile(path.join(SESSION_DIR, file), 'utf8'))
- sessionFiles.push(session)
- }
- }
+export async function saveSession(browser: WebdriverIO.Browser, sessionName: string) {
+ const { capabilities, requestedCapabilities, sessionId, options } = browser
+ await fs.writeFile(
+ getSessionFilePath(sessionName),
+ JSON.stringify({ name: sessionName, capabilities, requestedCapabilities, sessionId, options } as SessionFile)
+ )
+}
- return sessionFiles
- }
+export async function listSessions(): Promise {
+ const files = await fs.readdir(SESSION_DIR)
+ const sessionFiles: SessionFile[] = []
- static async deleteSession(sessionName?: string) {
- if (!sessionName) {
- throw new Error('Please provide a session name')
+ for (const file of files) {
+ if (file.startsWith(SESSION_FILE_PREFIX)) {
+ const session = JSON.parse(await fs.readFile(path.join(SESSION_DIR, file), 'utf8'))
+ sessionFiles.push(session)
}
+ }
- const sessionFilePath = getSessionFilePath(sessionName)
- const sessionExists = await fs.access(sessionFilePath).then(() => true, () => false)
- if (!sessionExists) {
- throw new Error(`Session "${sessionName}" not found`)
- }
+ return sessionFiles
+}
- await fs.unlink(sessionFilePath)
+export async function deleteSession(sessionName?: string) {
+ if (!sessionName) {
+ throw new Error('Please provide a session name')
}
- static async deleteAllSessions() {
- const files = await fs.readdir(SESSION_DIR)
- return Promise.all(files.map(async (file) => {
- if (!file.startsWith(SESSION_FILE_PREFIX)) {
- return
- }
- const sessionName = path.basename(file, path.extname(file)).replace(SESSION_FILE_PREFIX, '')
- const session = await SessionManager.loadSession(sessionName)
- await fs.unlink(path.join(SESSION_DIR, file))
- await session.deleteSession()
- }))
+ const sessionFilePath = getSessionFilePath(sessionName)
+ const sessionExists = await fs.access(sessionFilePath).then(() => true, () => false)
+ if (!sessionExists) {
+ throw new Error(`Session "${sessionName}" not found`)
}
+
+ await fs.unlink(sessionFilePath)
+}
+
+export async function deleteAllSessions() {
+ const files = await fs.readdir(SESSION_DIR)
+ return Promise.all(files.map(async (file) => {
+ if (!file.startsWith(SESSION_FILE_PREFIX)) {
+ return
+ }
+ const sessionName = path.basename(file, path.extname(file)).replace(SESSION_FILE_PREFIX, '')
+ const session = await loadSession(sessionName)
+ await fs.unlink(path.join(SESSION_DIR, file))
+ await session.deleteSession()
+ }))
}
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
index aad9b5c..00c2511 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,7 +1,7 @@
import path from 'node:path'
import { remote } from 'webdriverio'
-import { SUPPORTED_FILE_EXTENSIONS } from './constants.js'
+import { SUPPORTED_FILE_EXTENSIONS, DEFAULT_BROWSER } from './constants.js'
import type { RunnerArgs } from './types.js'
export function getHeadlessArgs ({ browserName, headless }: Pick) {
@@ -54,10 +54,7 @@ export async function initBrowserSession (params: RunnerArgs) {
}
export function parseRunArgs (args: RunnerArgs) {
- const browserName = args.browserName
- if (!browserName) {
- throw new Error('Please provide a browser name')
- }
+ const browserName = args.browserName ?? DEFAULT_BROWSER
const browserVersion = args.browserVersion
const headless = args.headless ?? true
const rootDir = args.rootDir ?? process.cwd()