diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ff4536..ceac69b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,15 +8,58 @@ on: workflow_dispatch: {} jobs: - lint: - uses: ./.github/workflows/node.yml - with: - run: npm run lint - test: - uses: ./.github/workflows/node.yml - with: - run: npm run -- test -p 360000 + build: - uses: ./.github/workflows/node.yml - with: - run: npm run build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: 'npm' + - run: npm config set registry ${{ vars.NPM_CI_REGISTRY }} + if: ${{ vars.NPM_CI_REGISTRY }} + - run: | + npm ci + npm run build + + ci: + runs-on: ubuntu-latest + strategy: + max-parallel: ${{ vars.CI_PARALLEL || 256 }} + matrix: + node-version: [16.x, 18.x, 20.x] + steps: + - uses: actions/checkout@v4 + - run: | + sudo apt-get update + sudo apt-get install -y libgconf-2-4 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm-dev libnss3-dev libxss-dev libasound2 + - uses: browser-actions/setup-chrome@latest + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm config set registry ${{ vars.NPM_CI_REGISTRY }} + if: ${{ vars.NPM_CI_REGISTRY }} + - run: | + npm ci + npm run -- test -t 360000 + npm run -- test -t 360000 -p e2e + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: 'npm' + - run: npm config set registry ${{ vars.NPM_CI_REGISTRY }} + if: ${{ vars.NPM_CI_REGISTRY }} + - run: | + npm ci + npm run lint + diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index b4bab90..0000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: End-to-end tests - -on: - schedule: - - cron: '20 4 * * *' - workflow_dispatch: {} - -jobs: - e2e: - uses: ./.github/workflows/node.yml - with: - run: npm run -- test -t 360000 -p e2e diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml deleted file mode 100644 index 0da2542..0000000 --- a/.github/workflows/node.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Continuous Integration - -on: - workflow_call: - inputs: - run: - required: true - type: string - -jobs: - node: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x, 20.x] - steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm config set registry ${{ vars.NPM_CI_REGISTRY }} - if: ${{ vars.NPM_CI_REGISTRY }} - - run: | - npm ci - ${{ inputs.run }} diff --git a/package-lock.json b/package-lock.json index dd8663f..ed245c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2832,6 +2832,155 @@ "node": ">= 8" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.0.0.tgz", + "integrity": "sha512-3PS82/5+tnpEaUWonjAFFvlf35QHF15xqyGd34GBa5oP5EPVfFXRsbSxIGYf1M+vZlqBZ3oxT1kRg9OYhtt8ng==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/@puppeteer/browsers/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@puppeteer/browsers/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@reduxjs/toolkit": { "version": "1.9.7", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", @@ -2977,6 +3126,12 @@ "node": ">= 6" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", @@ -4106,11 +4261,29 @@ "node": ">=14" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" }, + "node_modules/ast-types/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/asynciterator.prototype": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", @@ -4190,6 +4363,12 @@ "dequal": "^2.0.3" } }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, "node_modules/babel-loader": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", @@ -4268,6 +4447,13 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz", + "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==", + "dev": true, + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4295,6 +4481,15 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/basic-ftp": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", + "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -4391,12 +4586,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -4404,7 +4599,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -4413,14 +4608,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4574,9 +4761,9 @@ } }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } @@ -4819,6 +5006,19 @@ "node": ">=6.0" } }, + "node_modules/chromium-bidi": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.8.tgz", + "integrity": "sha512-blqh+1cEQbHBKmok3rVJkBlBxt9beKBgOsxbFgs7UJcoVbbeZ+K7+6liAsjgpc8l1Xd55cQUy14fXZdGSb4zIw==", + "dev": true, + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -5067,6 +5267,14 @@ "node": ">= 0.8.0" } }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5240,6 +5448,15 @@ } } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5401,6 +5618,15 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -5542,6 +5768,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -5822,6 +6062,12 @@ "node": "^14.14.0 || >=16.0.0" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1232444", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", + "integrity": "sha512-pM27vqEfxSxRkTMnF+XCmxSEb6duO5R+t8A9DEEJgy4Wz2RVanje2mmj99B6A3zv2r/qGfYlOvYznUhuokizmg==", + "dev": true + }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -6043,9 +6289,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/electron-to-chromium": { - "version": "1.4.664", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.664.tgz", - "integrity": "sha512-k9VKKSkOSNPvSckZgDDl/IQx45E1quMjX8QfLzUsAs/zve8AyFDK+ByRynSP/OfEfryiKHpQeMf00z0leLCc3A==" + "version": "1.4.665", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz", + "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==" }, "node_modules/electron/node_modules/@types/node": { "version": "18.19.15", @@ -6125,6 +6371,26 @@ "xmlhttprequest-ssl": "~2.0.0" } }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", @@ -6141,6 +6407,26 @@ "node": ">= 0.6" } }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", @@ -6272,24 +6558,28 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.16.tgz", + "integrity": "sha512-CREG2A9Vq7bpDRnldhFcMKuKArvkZtsH6Y0DHOHVg49qhf+LD8uEdUM3OkOAICv0EziGtDEnQtqY2/mfBILpFw==", "dependencies": { "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.6", "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.1", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", + "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" + "safe-array-concat": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-module-lexer": { @@ -7237,6 +7527,29 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -7245,11 +7558,36 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -7274,6 +7612,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -8041,6 +8385,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -10428,6 +10804,12 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -10721,6 +11103,15 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -11339,6 +11730,62 @@ "node": ">=6" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver/node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, "node_modules/pacote": { "version": "17.0.6", "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", @@ -11979,6 +12426,37 @@ "node": ">=10" } }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/precinct": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/precinct/-/precinct-11.0.5.tgz", @@ -12130,6 +12608,57 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -12147,6 +12676,58 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.0.0.tgz", + "integrity": "sha512-zYVnjwJngnSB4dbkWp7DHFSIc3nqHvZzrdHyo9+ugV1nq1Lm8obOMcmCFaGfR3PJs0EmYNz+/skBeO45yvASCQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.0.0", + "cosmiconfig": "9.0.0", + "puppeteer-core": "22.0.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.0.0.tgz", + "integrity": "sha512-S3s91rLde0A86PWVeNY82h+P0fdS7CTiNWAicCVH/bIspRP4nS2PnO5j+VTFqCah0ZJizGzpVPAmxVYbLxTc9w==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "2.0.0", + "chromium-bidi": "0.5.8", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1232444", + "ws": "8.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -12180,6 +12761,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -12221,9 +12808,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12234,14 +12821,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -13830,6 +14409,26 @@ "ws": "~8.11.0" } }, + "node_modules/socket.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -13957,9 +14556,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/spdy": { @@ -14494,6 +15093,19 @@ "readable-stream": "^3.5.0" } }, + "node_modules/streamx": { + "version": "2.15.8", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.8.tgz", + "integrity": "sha512-6pwMeMY/SuISiRsuS8TeIrAzyFbG5gGPHFQsYjUr/pbBadaL1PCWmzKw+CHZSwainfvcF6Si6cVLq4XTEwswFQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -14746,34 +15358,25 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, "dependencies": { - "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/tar/node_modules/fs-minipass": { @@ -14932,6 +15535,12 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -15211,6 +15820,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -15350,6 +15993,12 @@ "punycode": "^2.1.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -15704,26 +16353,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", @@ -16092,15 +16721,15 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -16733,6 +17362,7 @@ "assert": "^2.1.0", "autoprefixer": "^10.4.17", "before-build-webpack": "^0.2.13", + "body-parser": "^1.20.2", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "clean-webpack-plugin": "4.0.0", @@ -16760,7 +17390,8 @@ }, "devDependencies": { "@flecks/build": "^4.0.5", - "@flecks/fleck": "^4.0.5" + "@flecks/fleck": "^4.0.5", + "puppeteer": "^22.0.0" } } } diff --git a/packages/build/build/commands.js b/packages/build/build/commands.js index ebcd98a..0414e27 100644 --- a/packages/build/build/commands.js +++ b/packages/build/build/commands.js @@ -261,11 +261,12 @@ exports.commands = (program, flecks) => { const spawnWebpack = () => { webpack = spawnWith(cmd, options); webpack.on('message', (message) => { - switch (message) { + switch (message.type) { case 'kill': debug('killing...'); webpack.kill(); watcher.close(); + process.exitCode = message.payload; break; case 'restart': debug('restarting webpack...'); diff --git a/packages/build/build/default.eslint.config.js b/packages/build/build/default.eslint.config.js index 485130e..1faac58 100644 --- a/packages/build/build/default.eslint.config.js +++ b/packages/build/build/default.eslint.config.js @@ -41,6 +41,7 @@ module.exports = async (flecks) => ({ 'test/**/*.js', ], rules: { + 'prefer-arrow-callback': ['error', {allowNamedFunctions: true}], 'brace-style': 'off', 'class-methods-use-this': 'off', 'import/no-extraneous-dependencies': 'off', diff --git a/packages/build/build/flecks.bootstrap.js b/packages/build/build/flecks.bootstrap.js index b895456..5bd92f5 100644 --- a/packages/build/build/flecks.bootstrap.js +++ b/packages/build/build/flecks.bootstrap.js @@ -26,9 +26,11 @@ exports.hooks = { } config.plugins.push(new ProcessAssets(target, flecks)); }, - '@flecks/build.config.alter': async ({test}) => { + '@flecks/build.config.alter': async ({test}, env, argv, flecks) => { if (test) { + // Externalize the rest. test.externals = await externals({ + additionalModuleDirs: flecks.resolver.modules, allowlist: Object.keys(test.resolve.fallback).map((fallback) => new RegExp(fallback)), }); } diff --git a/packages/core/build/testing.js b/packages/core/build/testing.js index 78cc972..1c251d2 100644 --- a/packages/core/build/testing.js +++ b/packages/core/build/testing.js @@ -1,7 +1,7 @@ import {randomBytes} from 'crypto'; import {mkdir} from 'fs/promises'; import {tmpdir} from 'os'; -import {join} from 'path'; +import {basename, join} from 'path'; import {rimraf} from 'rimraf'; @@ -12,8 +12,23 @@ export function id() { } export async function createWorkspace() { - const workspace = join(tmpdir(), '@flecks', 'core', 'testing', await id()); + let workspace = join(tmpdir(), '@flecks', 'core', 'testing', await id()); + try { + throw new Error(); + } + catch (error) { + workspace += `-${basename( + error.stack + .split('\n').slice(-1)[0] + .split('at ')[1] + .match(/\((.*)\)$/)[1] + .split(':').slice(-3, -2)[0], + )}`; + } await mkdir(workspace, {recursive: true}); + process.on('exit', () => { + rimraf.sync(workspace); + }); // sheeeeesh process.prependListener('message', async (message) => { if ('__workerpool-terminate__' === message) { diff --git a/packages/fleck/build/commands.js b/packages/fleck/build/commands.js index 09b31bc..b243142 100644 --- a/packages/fleck/build/commands.js +++ b/packages/fleck/build/commands.js @@ -1,10 +1,10 @@ const {access} = require('fs/promises'); -const {join, relative} = require('path'); +const {join} = require('path'); const {commands: coreCommands} = require('@flecks/build/build/commands'); const {rimraf} = require('@flecks/build/src/server'); const {D} = require('@flecks/core/src'); -const {glob} = require('@flecks/core/src/server'); +const {glob, pipesink, processCode} = require('@flecks/core/src/server'); const Mocha = require('mocha'); const {watchParallelRun} = require('mocha/lib/cli/watch-run'); @@ -12,6 +12,7 @@ const debug = D('@flecks/build.commands'); const { FLECKS_CORE_ROOT = process.cwd(), + TERM, } = process.env; module.exports = (program, flecks) => { @@ -41,47 +42,36 @@ module.exports = (program, flecks) => { watch, } = opts; const {build} = coreCommands(program, flecks); - let files = []; - if (platforms.includes('default')) { - files.push(...await glob(join(FLECKS_CORE_ROOT, 'test', '*.js'))); - } - await Promise.all( - platforms - .filter((platform) => 'default' !== platform) - .map(async (platform) => { - files.push(...await glob(join(FLECKS_CORE_ROOT, 'test', platform, '*.js'))); - }), - ); - if (0 === files.length) { - // eslint-disable-next-line no-console - console.log('No tests found.'); - return undefined; - } - files = files.map((path) => relative(FLECKS_CORE_ROOT, path)); - if (only) { - if (files.includes(only)) { - files = [only]; - } - else { - throw new Error(`Test '${only}' does not exist!`); - } - } - files = files.map((file) => join('dist', file)); - // Remove the previous test. + // Remove the previous test(s). await rimraf(join(FLECKS_CORE_ROOT, 'dist', 'test')); // Kick off building the test and wait for the file to exist. - await build.action( + const child = await build.action( 'test', { - env: {FLECKS_CORE_TEST_PLATFORMS: JSON.stringify(platforms)}, + env: { + FLECKS_CORE_TEST_PLATFORMS: JSON.stringify(platforms), + FORCE_COLOR: 'dumb' !== TERM, + }, production, - stdio: 'ignore', + stdio: watch ? 'inherit' : 'pipe', watch, }, ); + if (!watch) { + const stdout = pipesink(child.stdout); + if (0 !== await processCode(child)) { + const buffer = await stdout; + if (!process.stdout.write(buffer)) { + await new Promise((resolve, reject) => { + process.stdout.on('error', reject); + process.stdout.on('drain', resolve); + }); + } + program.error('\nbuilding tests failed!\n'); + } + } debug('Testing...', opts); - // eslint-disable-next-line no-constant-condition - while (true) { + while (watch) { try { // eslint-disable-next-line no-await-in-loop await access(join(FLECKS_CORE_ROOT, 'dist', 'test')); @@ -94,6 +84,19 @@ module.exports = (program, flecks) => { }); } } + let files = await glob(join(FLECKS_CORE_ROOT, 'dist', 'test', '**', '*.js')); + if (0 === files.length) { + return undefined; + } + if (only) { + const index = files.indexOf(join(FLECKS_CORE_ROOT, 'dist', only)); + if (-1 !== index) { + files = [files[index]]; + } + else { + throw new Error(`Test '${only}' does not exist!`); + } + } // Magic. require('@flecks/core/build/resolve')( { diff --git a/packages/server/build/start.js b/packages/server/build/start.js index 4dc9832..4380e1c 100644 --- a/packages/server/build/start.js +++ b/packages/server/build/start.js @@ -92,15 +92,17 @@ class StartServerPlugin { ...(inspectPort && {inspectPort}), }); this.worker = cluster.fork(env); + this.worker.on('exit', (code) => { + if (killOnExit) { + process.send({type: 'kill', payload: code}); + process.exit(code); + } + }); this.worker.on('disconnect', () => { if (this.worker.exitedAfterDisconnect) { // eslint-disable-next-line no-console console.error('[HMR] Restarting application...'); - process.send('restart'); - } - else if (killOnExit) { - process.send('kill'); - process.exit(0); + process.send({type: 'restart'}); } }); return new Promise((resolve, reject) => { diff --git a/packages/server/package.json b/packages/server/package.json index 7f2f4f8..f346869 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -19,7 +19,8 @@ }, "files": [ "entry.js", - "runtime.js" + "runtime.js", + "server.js" ], "dependencies": { "@flecks/core": "^4.0.5" diff --git a/packages/server/src/entry.js b/packages/server/src/entry.js index 1f5b3b6..af48be6 100644 --- a/packages/server/src/entry.js +++ b/packages/server/src/entry.js @@ -20,15 +20,9 @@ import {D, Flecks} from '@flecks/core'; } const debug = D('@flecks/server/entry'); debug('starting server...'); - try { - global.flecks = await Flecks.from({...runtime, flecks: await loadFlecks()}); - await global.flecks.invokeSequentialAsync('@flecks/server.up'); - debug('up!'); - } - catch (error) { - // eslint-disable-next-line no-console - console.error(error); - } + global.flecks = await Flecks.from({...runtime, flecks: await loadFlecks()}); + await global.flecks.invokeSequentialAsync('@flecks/server.up'); + debug('up!'); })(); if (module.hot) { diff --git a/packages/server/src/server.js b/packages/server/src/server.js new file mode 100644 index 0000000..a3612cb --- /dev/null +++ b/packages/server/src/server.js @@ -0,0 +1,49 @@ +import cluster from 'cluster'; +import {createConnection} from 'net'; + +const { + FLECKS_SERVER_TEST_SOCKET, + NODE_ENV, +} = process.env; + +export const hooks = { + '@flecks/server.up': (flecks) => { + if (!FLECKS_SERVER_TEST_SOCKET || 'test' !== NODE_ENV) { + return; + } + const socket = createConnection(FLECKS_SERVER_TEST_SOCKET); + if (cluster.isWorker) { + cluster.worker.on('disconnect', () => { + socket.end(); + }); + } + flecks.server.socket = socket; + socket.on('connect', () => { + socket.on('data', (data) => { + const {meta, payload, type} = JSON.parse(data); + switch (type) { + case 'config.get': + socket.write(JSON.stringify({ + meta, + payload: flecks.get(payload), + })); + break; + case 'exit': + socket.end(); + process.exit(payload); + break; + default: + } + }); + }); + }, +}; + +export const mixin = (Flecks) => class FlecksWithServer extends Flecks { + + constructor(runtime) { + super(runtime); + this.server = {}; + } + +}; diff --git a/packages/server/test/server/build/build.js b/packages/server/test/server/build/build.js index bee23f7..3a49960 100644 --- a/packages/server/test/server/build/build.js +++ b/packages/server/test/server/build/build.js @@ -2,9 +2,14 @@ import {cp} from 'fs/promises'; import {join} from 'path'; import {createWorkspace} from '@flecks/core/build/testing'; -import {binaryPath, processCode, spawnWith} from '@flecks/core/server'; +import { + binaryPath, + pipesink, + processCode, + spawnWith, +} from '@flecks/core/server'; -import {listen} from './listen'; +import {socketListener} from './listen'; const { FLECKS_CORE_ROOT = process.cwd(), @@ -18,43 +23,85 @@ export async function createApplication() { return workspace; } -export async function buildChild(path, {args = [], opts = {}} = {}) { - return spawnWith( +class TestingServer { + + constructor(path, child, socketServer) { + this.path = path; + this.child = child; + this.socketServer = socketServer; + } + + async waitForSocket(options) { + return this.socketServer.waitForSocket(options); + } + +} + +export async function startServer({ + args = ['-h'], + beforeBuild, + failOnErrorCode = true, + opts = {}, + path: request, + task, +} = {}) { + let previousTimeout; + const start = Date.now(); + if (task) { + previousTimeout = task.timeout(); + task.timeout(0); + } + const {socketPath, socketServer} = await socketListener(); + const path = request || await createApplication(); + if (beforeBuild) { + await beforeBuild({path, task}); + } + const server = spawnWith( [await binaryPath('flecks', '@flecks/build'), 'build', ...args], { - stdio: 'ignore', + stdio: 'pipe', ...opts, env: { FLECKS_ENV__flecks_server__stats: '{"preset": "none"}', - FLECKS_ENV__flecks_server__start: 0, + FLECKS_ENV__flecks_server__start: true, FLECKS_CORE_ROOT: path, + FLECKS_SERVER_TEST_SOCKET: socketPath, + NODE_ENV: 'test', NODE_PATH: join(FLECKS_CORE_ROOT, '..', '..', 'node_modules'), ...opts.env, }, }, ); -} - -export async function build(path, {args = [], opts = {}} = {}) { - return processCode(await buildChild(path, {args, opts})); -} - -export async function serverActions(path, actions) { - const {listening, path: socketPath, socketServer} = await listen(); - await listening; - const server = spawnWith( - ['node', join(path, 'dist', 'server')], - { - env: { - FLECKS_SERVER_TEST_SOCKET: socketPath, - NODE_PATH: join(FLECKS_CORE_ROOT, '..', '..', 'node_modules'), - }, - stdio: 'ignore', - }, + if (failOnErrorCode) { + const stderr = pipesink(server.stderr); + server.on('exit', async (code) => { + if (0 !== code) { + const buffer = await stderr; + if (!process.stderr.write(buffer)) { + await new Promise((resolve, reject) => { + process.stderr.on('error', reject); + process.stderr.on('drain', resolve); + }); + } + // eslint-disable-next-line no-console + console.error('\nserver process exited unexpectedly\n'); + process.exit(code); + } + }); + } + task?.timeout(previousTimeout + (Date.now() - start)); + return new TestingServer( + path, + server, + socketServer, ); - const [code, results] = await Promise.all([ - processCode(server), - socketServer.waitForSocket().then(async (socket) => { +} + +export function withServer(task, options) { + return async function withServer() { + const server = await startServer({...options, task: this}); + const socket = await server.waitForSocket({task: this}); + server.actions = async (actions) => { const results = []; await actions.reduce( (p, action) => ( @@ -68,7 +115,25 @@ export async function serverActions(path, actions) { Promise.resolve(), ); return results; - }), - ]); - return {code, results}; + }; + return task({server, socket}); + }; +} + +export async function build(path, {args = [], opts = {}} = {}) { + return processCode(spawnWith( + [await binaryPath('flecks', '@flecks/build'), 'build', ...args], + { + stdio: 'ignore', + ...opts, + env: { + FLECKS_ENV__flecks_server__stats: '{"preset": "none"}', + FLECKS_ENV__flecks_server__start: 0, + FLECKS_CORE_ROOT: path, + NODE_ENV: 'test', + NODE_PATH: join(FLECKS_CORE_ROOT, '..', '..', 'node_modules'), + ...opts.env, + }, + }, + )); } diff --git a/packages/server/test/server/build/listen.js b/packages/server/test/server/build/listen.js index 6999d74..1de0909 100644 --- a/packages/server/test/server/build/listen.js +++ b/packages/server/test/server/build/listen.js @@ -33,12 +33,12 @@ class SocketWrapper { }); } - async waitForHmr() { + async waitForAction(type) { return new Promise((resolve, reject) => { this.socket.on('error', reject); this.socket.on('data', (data) => { const action = JSON.parse(data.toString()); - if ('hmr' === action.type) { + if (action.type === type) { resolve(action); } }); @@ -47,31 +47,39 @@ class SocketWrapper { } -export async function listen() { +export async function socketListener() { const path = join(tmpdir(), 'flecks', 'ci', await id()); await mkdir(dirname(path), {recursive: true}); const server = createServer(); server.listen(path); - server.waitForSocket = () => ( + server.waitForSocket = ({task, timeout = 30000} = {}) => ( new Promise((resolve, reject) => { - server.on('error', reject); + let previousTimeout; + const start = Date.now(); + if (task) { + previousTimeout = task.timeout(); + task.timeout(0); + } + const handle = setTimeout(() => { + reject(new Error('timeout waiting for IPC connection')); + }, timeout); + const finish = () => { + clearTimeout(handle); + task?.timeout(previousTimeout + (Date.now() - start)); + }; + server.on('error', (error) => { + finish(); + reject(error); + }); server.on('connection', (socket) => { + finish(); resolve(new SocketWrapper(socket)); }); }) ); - return { - listening: new Promise((resolve, reject) => { - server.on('error', reject); - server.on('listening', resolve); - }), - path, - socketServer: server, - }; -} - -export async function socketListener() { - const {listening, path: socketPath, socketServer} = await listen(); - await listening; - return {socketServer, socketPath}; + await new Promise((resolve, reject) => { + server.on('error', reject); + server.on('listening', resolve); + }); + return {socketServer: server, socketPath: path}; } diff --git a/packages/server/test/server/config-fail.js b/packages/server/test/server/config-fail.js index 2e734f6..b3f0ddb 100644 --- a/packages/server/test/server/config-fail.js +++ b/packages/server/test/server/config-fail.js @@ -1,38 +1,15 @@ import {join} from 'path'; -import {heavySetup} from '@flecks/core/build/testing'; import {writeFile} from '@flecks/core/server'; import {expect} from 'chai'; -import {build, createApplication} from './build/build'; -import {socketListener} from './build/listen'; +import {withServer} from './build/build'; -let path; -let socket; - -before(heavySetup(async () => { - path = await createApplication(); - const {socketPath, socketServer} = await socketListener(); - build( - path, - { - args: ['-h'], - opts: { - env: { - FLECKS_ENV__flecks_server__start: true, - FLECKS_SERVER_TEST_SOCKET: socketPath, - }, - }, - }, - ); - socket = await socketServer.waitForSocket(); -})); - -it('updates config', async () => { +it('allows updates to fail', withServer(async ({server, socket}) => { expect((await socket.send({type: 'config.get', payload: 'comm.foo'})).payload) .to.equal('bar'); await writeFile( - join(path, 'build', 'flecks.yml'), + join(server.path, 'build', 'flecks.yml'), ` '@flecks/build': {} '@flecks/core': {} @@ -40,12 +17,12 @@ it('updates config', async () => { 'comm:./comm': {foo: 'baz'} `, ); - await socket.waitForHmr(); + await socket.waitForAction('hmr'); expect((await socket.send({type: 'config.get', payload: 'comm.foo'})).payload) .to.equal('baz'); let restarted; const whatHappened = Promise.race([ - socket.waitForHmr() + socket.waitForAction('hmr') .then(() => { restarted = false; }) @@ -58,7 +35,7 @@ it('updates config', async () => { }), ]); await writeFile( - join(path, 'build', 'flecks.yml'), + join(server.path, 'build', 'flecks.yml'), ` '@flecks/build': {} '@flecks/core': {} @@ -69,4 +46,4 @@ it('updates config', async () => { await whatHappened; expect(restarted) .to.be.true; -}); +})); diff --git a/packages/server/test/server/config-restart.js b/packages/server/test/server/config-restart.js index 3d684e9..00e007d 100644 --- a/packages/server/test/server/config-restart.js +++ b/packages/server/test/server/config-restart.js @@ -1,40 +1,14 @@ import {join} from 'path'; -import {heavySetup} from '@flecks/core/build/testing'; import {writeFile} from '@flecks/core/server'; import {expect} from 'chai'; -import {buildChild, createApplication} from './build/build'; -import {socketListener} from './build/listen'; +import {withServer} from './build/build'; -let path; -let listener; -let socket; - -before(heavySetup(async () => { - path = await createApplication(); - listener = await socketListener(); - const {socketPath, socketServer} = listener; - await buildChild( - path, - { - args: ['-w'], - opts: { - env: { - FLECKS_ENV__flecks_server__start: true, - FLECKS_SERVER_TEST_SOCKET: socketPath, - }, - }, - }, - ); - socket = await socketServer.waitForSocket(); -})); - -async function restart() { - this.timeout(0); +it('restarts when config keys change', withServer(async ({server, socket}) => { let restarted; const whatHappened = Promise.race([ - socket.waitForHmr() + socket.waitForAction('hmr') .then(() => { restarted = false; }) @@ -47,7 +21,7 @@ async function restart() { }), ]); await writeFile( - join(path, 'build', 'flecks.yml'), + join(server.path, 'build', 'flecks.yml'), ` '@flecks/build': {} '@flecks/core': {} @@ -60,15 +34,10 @@ async function restart() { expect(restarted) .to.be.true; let config; - const before = Date.now(); - await listener.socketServer.waitForSocket() + await server.socketServer.waitForSocket({task: this}) .then(async (socket) => { ({payload: config} = await socket.send({type: 'config.get', payload: '@flecks/repl/server'})); }); - // Had to rebuild... - this.timeout(2000 + (Date.now() - before)); expect(config) .to.not.be.undefined; -} - -it('restarts when config keys change', restart); +})); diff --git a/packages/server/test/server/config-update.js b/packages/server/test/server/config-update.js index 44eb943..3b6b5a4 100644 --- a/packages/server/test/server/config-update.js +++ b/packages/server/test/server/config-update.js @@ -1,38 +1,15 @@ import {join} from 'path'; -import {heavySetup} from '@flecks/core/build/testing'; import {writeFile} from '@flecks/core/server'; import {expect} from 'chai'; -import {build, createApplication} from './build/build'; -import {socketListener} from './build/listen'; +import {withServer} from './build/build'; -let path; -let socket; - -before(heavySetup(async () => { - path = await createApplication(); - const {socketPath, socketServer} = await socketListener(); - build( - path, - { - args: ['-h'], - opts: { - env: { - FLECKS_ENV__flecks_server__start: true, - FLECKS_SERVER_TEST_SOCKET: socketPath, - }, - }, - }, - ); - socket = await socketServer.waitForSocket(); -})); - -it('updates config', async () => { +it('updates config', withServer(async ({server, socket}) => { expect((await socket.send({type: 'config.get', payload: '@flecks/core.id'})).payload) .to.equal('flecks'); await writeFile( - join(path, 'build', 'flecks.yml'), + join(server.path, 'build', 'flecks.yml'), ` '@flecks/build': {} '@flecks/core': {id: 'testing'} @@ -40,8 +17,8 @@ it('updates config', async () => { 'comm:./comm': {} `, ); - await socket.waitForHmr(); + await socket.waitForAction('hmr'); expect((await socket.send({type: 'config.get', payload: '@flecks/core.id'})).payload) .to.equal('testing'); await socket.send({type: 'exit'}); -}); +})); diff --git a/packages/server/test/server/runtime-config-bootstrap.js b/packages/server/test/server/runtime-config-bootstrap.js index e9b08ce..3620995 100644 --- a/packages/server/test/server/runtime-config-bootstrap.js +++ b/packages/server/test/server/runtime-config-bootstrap.js @@ -1,48 +1,46 @@ import {mkdir} from 'fs/promises'; import {join} from 'path'; -import {heavySetup} from '@flecks/core/build/testing'; import {writeFile} from '@flecks/core/server'; import {expect} from 'chai'; -import {build, createApplication, serverActions} from './build/build'; +import {withServer} from './build/build'; -let path; - -before(heavySetup(async () => { - path = await createApplication(); - await mkdir(join(path, 'server-only', 'build'), {recursive: true}); - await writeFile(join(path, 'server-only', 'package.json'), '{}'); - const config = ` - exports.hooks = { - '@flecks/core.config': () => ({ - foo: 'bar', - blah: {one: 2, three: 4}, - }), - }; - `; - await writeFile(join(path, 'server-only', 'build', 'flecks.bootstrap.js'), config); - await writeFile( - join(path, 'build', 'flecks.yml'), - ` - '@flecks/build': {} - '@flecks/core': {} - '@flecks/server': {} - 'comm:./comm': {} - 'server-only:./server-only': {foo: 'baz'} - `, - ); - await build(path, {args: ['-d']}); -})); - -it('propagates bootstrap config', async () => { - const {results: [{payload: foo}, {payload: blah}]} = await serverActions(path, [ - {type: 'config.get', payload: 'server-only.foo'}, - {type: 'config.get', payload: 'server-only.blah'}, - {type: 'exit'}, - ]); - expect(foo) - .to.equal('baz'); - expect(blah) - .to.deep.equal({one: 2, three: 4}); -}); +it('propagates bootstrap config', withServer( + async ({server}) => { + const [{payload: foo}, {payload: blah}] = await server.actions([ + {type: 'config.get', payload: 'server-only.foo'}, + {type: 'config.get', payload: 'server-only.blah'}, + {type: 'exit'}, + ]); + expect(foo) + .to.equal('baz'); + expect(blah) + .to.deep.equal({one: 2, three: 4}); + }, + { + beforeBuild: async ({path}) => { + await mkdir(join(path, 'server-only', 'build'), {recursive: true}); + await writeFile(join(path, 'server-only', 'package.json'), '{}'); + const config = ` + exports.hooks = { + '@flecks/core.config': () => ({ + foo: 'bar', + blah: {one: 2, three: 4}, + }), + }; + `; + await writeFile(join(path, 'server-only', 'build', 'flecks.bootstrap.js'), config); + await writeFile( + join(path, 'build', 'flecks.yml'), + ` + '@flecks/build': {} + '@flecks/core': {} + '@flecks/server': {} + 'comm:./comm': {} + 'server-only:./server-only': {foo: 'baz'} + `, + ); + }, + }, +)); diff --git a/packages/server/test/server/runtime-config-override.js b/packages/server/test/server/runtime-config-override.js index a6d106b..ae1af39 100644 --- a/packages/server/test/server/runtime-config-override.js +++ b/packages/server/test/server/runtime-config-override.js @@ -1,35 +1,33 @@ import {join} from 'path'; -import {heavySetup} from '@flecks/core/build/testing'; import {writeFile} from '@flecks/core/server'; import {expect} from 'chai'; -import {build, createApplication, serverActions} from './build/build'; +import {withServer} from './build/build'; -let path; - -before(heavySetup(async () => { - path = await createApplication(); - await writeFile( - join(path, 'build', 'flecks.yml'), - ` - '@flecks/build': {} - '@flecks/core': {id: 'testing'} - '@flecks/server': {} - 'comm:./comm': {foo: 'baz'} - `, - ); - await build(path, {args: ['-d']}); -})); - -it('propagates bootstrap config', async () => { - const {results: [{payload: id}, {payload: foo}]} = await serverActions(path, [ - {type: 'config.get', payload: '@flecks/core.id'}, - {type: 'config.get', payload: 'comm.foo'}, - {type: 'exit'}, - ]); - expect(id) - .to.equal('testing'); - expect(foo) - .to.equal('baz'); -}); +it('propagates bootstrap config', withServer( + async ({server}) => { + const [{payload: id}, {payload: foo}] = await server.actions([ + {type: 'config.get', payload: '@flecks/core.id'}, + {type: 'config.get', payload: 'comm.foo'}, + {type: 'exit'}, + ]); + expect(id) + .to.equal('testing'); + expect(foo) + .to.equal('baz'); + }, + { + beforeBuild: async ({path}) => { + await writeFile( + join(path, 'build', 'flecks.yml'), + ` + '@flecks/build': {} + '@flecks/core': {id: 'testing'} + '@flecks/server': {} + 'comm:./comm': {foo: 'baz'} + `, + ); + }, + }, +)); diff --git a/packages/server/test/server/runtime-config-runtime.js b/packages/server/test/server/runtime-config-runtime.js index f6f6ee9..3b0ed41 100644 --- a/packages/server/test/server/runtime-config-runtime.js +++ b/packages/server/test/server/runtime-config-runtime.js @@ -1,20 +1,12 @@ -import {heavySetup} from '@flecks/core/build/testing'; import {expect} from 'chai'; -import {build, createApplication, serverActions} from './build/build'; +import {withServer} from './build/build'; -let path; - -before(heavySetup(async () => { - path = await createApplication(); - await build(path, {args: ['-d']}); -})); - -it('propagates runtime config', async () => { - const {results: [{payload: foo}]} = await serverActions(path, [ +it('propagates runtime config', withServer(async ({server}) => { + const [{payload: foo}] = await server.actions([ {type: 'config.get', payload: 'comm.foo'}, {type: 'exit'}, ]); expect(foo) .to.equal('bar'); -}); +})); diff --git a/packages/server/test/server/runtime-connect.js b/packages/server/test/server/runtime-connect.js index 9dea1c4..f28a3b2 100644 --- a/packages/server/test/server/runtime-connect.js +++ b/packages/server/test/server/runtime-connect.js @@ -1,19 +1,16 @@ -import {heavySetup} from '@flecks/core/build/testing'; +import {processCode} from '@flecks/core/src/server'; import {expect} from 'chai'; -import {build, createApplication, serverActions} from './build/build'; +import {withServer} from './build/build'; -let path; - -before(heavySetup(async () => { - path = await createApplication(); - await build(path, {args: ['-d']}); -})); - -it('connects', async () => { - const {code} = await serverActions(path, [ - {type: 'exit', payload: 42}, - ]); - expect(code) - .to.equal(42); -}); +it('connects', withServer( + async ({server}) => { + const code = processCode(server.child); + await server.actions([ + {type: 'exit', payload: 42}, + ]); + expect(await code) + .to.equal(42); + }, + {failOnErrorCode: false}, +)); diff --git a/packages/server/test/server/source-restart.js b/packages/server/test/server/source-restart.js index f902fda..0c93756 100644 --- a/packages/server/test/server/source-restart.js +++ b/packages/server/test/server/source-restart.js @@ -1,37 +1,14 @@ import {join} from 'path'; -import {heavySetup} from '@flecks/core/build/testing'; import {writeFile} from '@flecks/core/server'; import {expect} from 'chai'; -import {buildChild, createApplication} from './build/build'; -import {socketListener} from './build/listen'; +import {withServer} from './build/build'; -let path; -let socket; - -before(heavySetup(async () => { - path = await createApplication(); - const {socketPath, socketServer} = await socketListener(); - await buildChild( - path, - { - args: ['-w'], - opts: { - env: { - FLECKS_ENV__flecks_server__start: true, - FLECKS_SERVER_TEST_SOCKET: socketPath, - }, - }, - }, - ); - socket = await socketServer.waitForSocket(); -})); - -it('restarts when root sources change', async () => { +it('restarts when root sources change', withServer(async ({server, socket}) => { let restarted; const whatHappened = Promise.race([ - socket.waitForHmr() + socket.waitForAction('hmr') .then(() => { restarted = false; }) @@ -43,8 +20,8 @@ it('restarts when root sources change', async () => { }); }), ]); - await writeFile(join(path, 'comm', 'package.json'), '{}'); + await writeFile(join(server.path, 'comm', 'package.json'), '{}'); await whatHappened; expect(restarted) .to.be.true; -}); +})); diff --git a/packages/server/test/server/template/comm/src/server.js b/packages/server/test/server/template/comm/src/server.js index a5dc99e..a1a7112 100644 --- a/packages/server/test/server/template/comm/src/server.js +++ b/packages/server/test/server/template/comm/src/server.js @@ -1,10 +1,3 @@ -import cluster from 'cluster'; -import {createConnection} from 'net'; - -const { - FLECKS_SERVER_TEST_SOCKET, -} = process.env; - export const hooks = { '@flecks/core.reload': (fleck, config) => { if ('comm' === fleck && 'fail' === config.foo) { @@ -12,42 +5,10 @@ export const hooks = { } }, '@flecks/core.hmr': async (path, M, flecks) => { - if (!flecks.socket) { - return; - } - flecks.socket.write(JSON.stringify({ + const {socket} = flecks.server; + socket.write(JSON.stringify({ type: 'hmr', payload: path, })); }, - '@flecks/server.up': async (flecks) => { - if (!FLECKS_SERVER_TEST_SOCKET) { - return; - } - const socket = createConnection(FLECKS_SERVER_TEST_SOCKET); - if (cluster.isWorker) { - cluster.worker.on('disconnect', () => { - socket.end(); - }); - } - flecks.socket = socket; - socket.on('connect', () => { - socket.on('data', (data) => { - const {meta, payload, type} = JSON.parse(data); - switch (type) { - case 'config.get': - socket.write(JSON.stringify({ - meta, - payload: flecks.get(payload), - })); - break; - case 'exit': - socket.end(); - process.exit(payload); - break; - default: - } - }); - }); - }, }; diff --git a/packages/web/build/flecks.bootstrap.js b/packages/web/build/flecks.bootstrap.js index 698ea3f..8384922 100644 --- a/packages/web/build/flecks.bootstrap.js +++ b/packages/web/build/flecks.bootstrap.js @@ -1,15 +1,23 @@ const {stat, unlink} = require('fs/promises'); -const {join} = require('path'); +const { + basename, + dirname, + extname, + join, + relative, +} = require('path'); const Build = require('@flecks/build/build/build'); const {regexFromExtensions} = require('@flecks/build/src/server'); -const {binaryPath, spawnWith} = require('@flecks/core/src/server'); +const {binaryPath, glob, spawnWith} = require('@flecks/core/src/server'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { FLECKS_CORE_ROOT = process.cwd(), } = process.env; +const tests = join(FLECKS_CORE_ROOT, 'test'); + exports.hooks = { '@flecks/build.config': async (target, config, env, argv, flecks) => { const isProduction = 'production' === argv.mode; @@ -25,6 +33,19 @@ exports.hooks = { config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'})); break; } + case 'test': { + finalLoader = {loader: MiniCssExtractPlugin.loader}; + config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'})); + (await glob(join(tests, 'client', '*.js'))) + .forEach((path) => { + const entry = relative(tests, path); + config.entry[join(dirname(entry), basename(entry, extname(entry)))] = [ + 'source-map-support/register', + path, + ]; + }); + break; + } case 'web': { if (isProduction) { finalLoader = {loader: MiniCssExtractPlugin.loader}; diff --git a/packages/web/build/runtime.js b/packages/web/build/runtime.js index fe3bc8c..c0262b3 100644 --- a/packages/web/build/runtime.js +++ b/packages/web/build/runtime.js @@ -1,20 +1,18 @@ -const {access, readFile} = require('fs/promises'); +const {access} = require('fs/promises'); const { basename, - dirname, extname, join, } = require('path'); const Build = require('@flecks/build/build/build'); -const {glob} = require('@flecks/core/server'); module.exports = async (config, env, argv, flecks) => { const buildFlecks = await Build.from({ config: flecks.realiasedConfig, platforms: ['client', '!server'], }); - const {resolver, flecks: webFlecks} = buildFlecks; + const {flecks: webFlecks} = buildFlecks; const paths = Object.keys(webFlecks) .filter((fleck) => !['@flecks/server'].includes(fleck)); const styles = ( @@ -44,7 +42,6 @@ module.exports = async (config, env, argv, flecks) => { ) .filter((filename) => !!filename); const runtime = await flecks.resolver.resolve(join('@flecks/web/runtime')); - const isProduction = 'production' === argv.mode; const resolvedPaths = (await Promise.all( paths.map(async (path) => [path, await flecks.resolver.resolve(path)]), )) @@ -98,56 +95,4 @@ module.exports = async (config, env, argv, flecks) => { await buildFlecks.runtimeCompiler('web', config, env, argv); // Styles. config.entry.index.push(...styles); - // Tests. - if (!isProduction) { - const testEntries = (await Promise.all( - buildFlecks.roots - .map(async ([root, request]) => { - const tests = []; - const resolved = dirname( - await buildFlecks.resolver.resolve(join(request, 'package.json')), - ); - const rootTests = await glob(join(resolved, 'test', '*.js')); - tests.push(...rootTests.map((test) => test.replace(resolved, root))); - const platformTests = await Promise.all( - buildFlecks.platforms.map((platform) => ( - glob(join(resolved, 'test', platform, '*.js')) - )), - ); - tests.push(...platformTests.flat().map((test) => test.replace(resolved, root))); - return [root, tests]; - }), - )) - .filter(([, tests]) => tests.length > 0); - const tests = await resolver.resolve( - join('@flecks/web', 'server', 'build', 'tests'), - ); - const testsSource = (await readFile(tests)).toString(); - config.module.rules.push({ - test: tests, - use: [ - { - loader: runtime, - options: { - source: testsSource.replace( - " await import('@flecks/web/tests');", - testEntries - .map(([root, tests]) => ( - [ - ` describe('${root}', () => {`, - ` ${tests.map((test) => `require('${test}');`).join('\n ')}`, - ' });', - ].join('\n') - )).join('\n\n'), - ), - }, - }, - ], - }); - // Fix a little derp in mocha 10.2.0. - config.module.rules.push({ - test: /mocha\/mocha\.js$/, - use: await flecks.resolver.resolve('@flecks/web/build/fix-mocha-critical-dependency'), - }); - } }; diff --git a/packages/web/build/web.webpack.config.js b/packages/web/build/web.webpack.config.js index 7dd3cd7..be42d29 100644 --- a/packages/web/build/web.webpack.config.js +++ b/packages/web/build/web.webpack.config.js @@ -15,6 +15,7 @@ const WaitForManifestPlugin = require('./wait-for-manifest'); const { FLECKS_CORE_ROOT = process.cwd(), + NODE_ENV, } = process.env; module.exports = async (env, argv, flecks) => { @@ -70,12 +71,6 @@ module.exports = async (env, argv, flecks) => { entry: '@flecks/web/server/build/entry', }], ]; - if (!isProduction) { - entries.push(['tests', { - entry: '@flecks/web/server/build/tests', - title: 'Testbed', - }]); - } await Promise.all( entries .map(async ([name, mainsConfig]) => { @@ -195,6 +190,7 @@ module.exports = async (env, argv, flecks) => { }, optimization: { minimize: isProduction, + nodeEnv: NODE_ENV, runtimeChunk: 'single', splitChunks: { cacheGroups: { diff --git a/packages/web/package.json b/packages/web/package.json index a63bcdb..41ef9f3 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -18,6 +18,7 @@ "access": "public" }, "files": [ + "client.js", "entry.js", "index.js", "runtime.js", @@ -29,11 +30,13 @@ "@babel/parser": "^7.17.0", "@babel/types": "^7.17.0", "@flecks/core": "^4.0.5", + "@flecks/server": "^4.0.5", "@webpack-cli/serve": "^2.0.5", "add-asset-html-webpack-plugin": "^6.0.0", "assert": "^2.1.0", "autoprefixer": "^10.4.17", "before-build-webpack": "^0.2.13", + "body-parser": "^1.20.2", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "clean-webpack-plugin": "4.0.0", @@ -42,8 +45,8 @@ "express": "^4.17.1", "html-loader": "^4.2.0", "html-webpack-plugin": "^5.5.3", - "husky": "^9.0.7", "http-proxy": "^1.17.0", + "husky": "^9.0.7", "mini-css-extract-plugin": "^2.7.6", "mocha": "^10.2.0", "null-loader": "^4.0.1", @@ -61,6 +64,7 @@ }, "devDependencies": { "@flecks/build": "^4.0.5", - "@flecks/fleck": "^4.0.5" + "@flecks/fleck": "^4.0.5", + "puppeteer": "^22.0.0" } } diff --git a/packages/web/src/client.js b/packages/web/src/client.js new file mode 100644 index 0000000..522d307 --- /dev/null +++ b/packages/web/src/client.js @@ -0,0 +1,24 @@ +export const mixin = (Flecks) => class FlecksWithWebClient extends Flecks { + + constructor(runtime) { + super(runtime); + if ('test' !== process.env.NODE_ENV) { + return; + } + this.web = { + test: ({payload, type}) => ( + fetch( + `/@flecks/web/testing?type=${type}`, + { + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }, + ) + ), + }; + } + +}; diff --git a/packages/web/src/server/http.js b/packages/web/src/server/http.js index c1a44e1..cdd8563 100644 --- a/packages/web/src/server/http.js +++ b/packages/web/src/server/http.js @@ -3,6 +3,7 @@ import {createServer, ServerResponse} from 'http'; import {join} from 'path'; import {D} from '@flecks/core'; +import bodyParser from 'body-parser'; import compression from 'compression'; import express from 'express'; import httpProxy from 'http-proxy'; @@ -31,6 +32,8 @@ export const createHttpServer = async (flecks) => { const httpServer = createServer(app); httpServer.app = app; flecks.web.server = httpServer; + // Body parser. + app.use(bodyParser.json()); // Compression. heheh app.use(compression({level: 'production' === NODE_ENV ? 6 : 9})); // Socket connection. diff --git a/packages/web/src/server/index.js b/packages/web/src/server/index.js index e193d1f..72a7312 100644 --- a/packages/web/src/server/index.js +++ b/packages/web/src/server/index.js @@ -1,19 +1,36 @@ import {configSource, inlineConfig} from './config'; import {createHttpServer} from './http'; +const { + NODE_ENV, +} = process.env; + export {configSource}; export const hooks = { - '@flecks/web.routes': (flecks) => [ - { - method: 'get', - path: '/flecks.config.js', - middleware: async (req, res) => { - res.setHeader('Content-Type', 'application/javascript; charset=UTF-8'); - res.send(await configSource(flecks, req)); + '@flecks/web.routes': (flecks) => { + const routes = [ + { + method: 'get', + path: '/flecks.config.js', + middleware: async (req, res) => { + res.setHeader('Content-Type', 'application/javascript; charset=UTF-8'); + res.send(await configSource(flecks, req)); + }, }, - }, - ], + ]; + if ('test' === NODE_ENV) { + routes.push({ + method: 'post', + path: '/@flecks/web/testing', + middleware: (req, res, next) => { + flecks.server.socket.write(JSON.stringify({payload: req.body, type: req.query.type})); + next(); + }, + }); + } + return routes; + }, '@flecks/web/server.stream.html': inlineConfig, '@flecks/server.up': (flecks) => createHttpServer(flecks), }; diff --git a/packages/web/test/client/up.js b/packages/web/test/client/up.js new file mode 100644 index 0000000..9d2fbd5 --- /dev/null +++ b/packages/web/test/client/up.js @@ -0,0 +1,41 @@ +import {expect} from 'chai'; + +import {withWeb} from '../helpers/with-web'; + +let report; + +const options = { + beforeConnect: ({socket}) => { + report = socket.waitForAction('report'); + }, +}; + +it('brings a client up', withWeb( + async function test({ + browser, + page, + response, + }) { + expect(response) + .to.not.be.null; + const { + payload: { + config, + id, + request, + }, + } = await report; + const appMountSelector = await page.waitForSelector(`#${id}`); + expect(await appMountSelector?.evaluate((el) => el.textContent)) + .to.equal('hello world'); + const yepSelector = await page.waitForSelector(`.${request}`); + expect(await yepSelector?.evaluate((el) => el.textContent)) + .to.equal('YEP'); + expect(config) + .to.deep.equal({why: 'hello there'}); + expect(request) + .to.equal('testing-value-value'); + await browser.close(); + }, + options, +)); diff --git a/packages/web/test/helpers/with-web.js b/packages/web/test/helpers/with-web.js new file mode 100644 index 0000000..8be0200 --- /dev/null +++ b/packages/web/test/helpers/with-web.js @@ -0,0 +1,69 @@ +import {startServer} from '@flecks/server/test/server/build/build'; +import puppeteer from 'puppeteer'; + +export async function connectBrowser(url, options = {}) { + let previousTimeout; + const start = Date.now(); + if (options.task) { + previousTimeout = options.task.timeout(); + options.task.timeout(0); + } + const {timeout = 30000} = options; + const browser = await puppeteer.launch({ + // For CI. + args: ['--no-sandbox'], + }); + const page = await browser.newPage(); + let response; + const handle = setTimeout(() => { + throw new Error(`timed out trying to connect browser to '${url}'!`); + }, timeout); + /* eslint-disable no-await-in-loop */ + while (!response) { + try { + response = await page.goto(url, {...options, timeout: timeout - (Date.now() - start)}); + if (response) { + break; + } + } + catch { + await new Promise((resolve) => { + setTimeout(resolve, 250); + }); + } + } + /* eslint-enable no-await-in-loop */ + clearTimeout(handle); + options.task?.timeout(previousTimeout + (Date.now() - start)); + return {browser, page, response}; +} + +export function withWeb(task, options) { + return async function withWeb() { + const server = await startServer({...options, task: this}); + const socket = await server.waitForSocket({...options, task: this}); + if (options.beforeConnect) { + await options.beforeConnect({server, socket}); + } + const start = Date.now(); + const previousTimeout = this.timeout(); + this.timeout(0); + const {payload: config} = await socket.send({type: 'config.get', payload: '@flecks/web'}); + this.timeout(previousTimeout + (Date.now() - start)); + const {browser, page, response} = await connectBrowser( + // @todo schema + `http://${config.public}`, + { + ...options, + task: this, + }, + ); + return task({ + browser, + page, + response, + server, + socket, + }); + }; +} diff --git a/packages/web/test/server/template/build/flecks.yml b/packages/web/test/server/template/build/flecks.yml new file mode 100644 index 0000000..da10f99 --- /dev/null +++ b/packages/web/test/server/template/build/flecks.yml @@ -0,0 +1,4 @@ +'@flecks/core': {} +'@flecks/server': {} +'@flecks/web': {} +'test:./test': {} diff --git a/packages/web/test/server/template/package.json b/packages/web/test/server/template/package.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/packages/web/test/server/template/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/web/test/server/template/test/package.json b/packages/web/test/server/template/test/package.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/packages/web/test/server/template/test/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/web/test/server/template/test/src/client.js b/packages/web/test/server/template/test/src/client.js new file mode 100644 index 0000000..39fafc1 --- /dev/null +++ b/packages/web/test/server/template/test/src/client.js @@ -0,0 +1,13 @@ +export const hooks = { + '@flecks/web/client.up': async (container, flecks) => { + container.innerHTML = 'hello world'; + await flecks.web.test({ + type: 'report', + payload: { + id: flecks.get('@flecks/web.appMountId'), + config: flecks.get('test'), + env: process.env.NODE_ENV, + }, + }); + }, +}; diff --git a/packages/web/test/server/template/test/src/index.js b/packages/web/test/server/template/test/src/index.js new file mode 100644 index 0000000..2499d9d --- /dev/null +++ b/packages/web/test/server/template/test/src/index.js @@ -0,0 +1,5 @@ +export const hooks = { + '@flecks/web.config': () => ({ + why: 'hello there', + }), +}; diff --git a/packages/web/test/server/template/test/src/server.js b/packages/web/test/server/template/test/src/server.js new file mode 100644 index 0000000..5d85103 --- /dev/null +++ b/packages/web/test/server/template/test/src/server.js @@ -0,0 +1,18 @@ +import {Readable} from 'stream'; + +import {pipesink} from '@flecks/core/server'; + +export const hooks = { + '@flecks/web/server.request.route': () => (req, res, next) => { + req.body.request += '-value'; + next(); + }, + '@flecks/web/server.request.socket': () => (req, res, next) => { + req.body.request = 'testing-value'; + next(); + }, + '@flecks/web/server.stream.html': async (stream, req) => { + const html = (await pipesink(stream)).toString(); + return Readable.from(html.replace('', `

YEP

`)); + }, +};