diff --git a/.github/workflows/check-e2e.yml b/.github/workflows/check-e2e.yml index 61eefc587..3518dbc29 100644 --- a/.github/workflows/check-e2e.yml +++ b/.github/workflows/check-e2e.yml @@ -36,10 +36,11 @@ jobs: if: steps.playwright-dep-cache.outputs.cache-hit != 'true' run: npx playwright install chromium firefox - - name: Start http-api test server + - name: Install Radicle binaries run: | mkdir -p tests/artifacts; - ./scripts/run-httpd-with-fixtures --non-interactive --download 2>&1 | tee tests/artifacts/httpd-${{ matrix.browser }}.log & + ./scripts/install-binaries; + ./scripts/install-binaries --show-path >> $GITHUB_PATH; - name: Run Playwright tests run: | diff --git a/.github/workflows/check-httpd-api-unit-test.yml b/.github/workflows/check-httpd-api-unit-test.yml index 2dbabc328..a270df7f1 100644 --- a/.github/workflows/check-httpd-api-unit-test.yml +++ b/.github/workflows/check-httpd-api-unit-test.yml @@ -18,8 +18,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - run: npm ci - - name: Start http-api test server + - name: Install Radicle binaries run: | mkdir -p tests/artifacts; - ./scripts/run-httpd-with-fixtures --non-interactive --download 2>&1 | tee tests/artifacts/httpd-api.log & - - run: npm run test:httpd-api:unit + ./scripts/install-binaries; + ./scripts/install-binaries --show-path >> $GITHUB_PATH; + - run: | + npm run test:httpd-api:unit diff --git a/.github/workflows/check-visual.yml b/.github/workflows/check-visual.yml index 8743b42a1..5ec0ba86c 100644 --- a/.github/workflows/check-visual.yml +++ b/.github/workflows/check-visual.yml @@ -54,10 +54,11 @@ jobs: if: steps.playwright-dep-cache.outputs.cache-hit != 'true' run: npx playwright install chromium firefox - - name: Start http-api test server + - name: Install Radicle binaries run: | mkdir -p tests/artifacts; - ./scripts/run-httpd-with-fixtures --non-interactive --download 2>&1 | tee tests/artifacts/httpd-visual.log & + ./scripts/install-binaries; + ./scripts/install-binaries --show-path >> $GITHUB_PATH; - name: Run Playwright tests run: | diff --git a/httpd-client/tests/project.test.ts b/httpd-client/tests/project.test.ts index d36e7920e..5b1445740 100644 --- a/httpd-client/tests/project.test.ts +++ b/httpd-client/tests/project.test.ts @@ -1,5 +1,7 @@ import { describe, test } from "vitest"; + import { HttpdClient } from "../index"; +import { aliceMainHead, aliceRemote, rid } from "@tests/support/fixtures"; const api = new HttpdClient({ hostname: "127.0.0.1", @@ -9,9 +11,7 @@ const api = new HttpdClient({ describe("project", () => { test("#getByDelegate(delegateId)", async () => { - await api.project.getByDelegate( - "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi", - ); + await api.project.getByDelegate(aliceRemote); }); test("#getAll()", async () => { @@ -19,62 +19,45 @@ describe("project", () => { }); test("#getById(id)", async () => { - await api.project.getById("rad:zKtT7DmF9H34KkvcKj9PHW19WzjT"); + await api.project.getById(rid); }); test("#getActivity(id)", async () => { - await api.project.getActivity("rad:zKtT7DmF9H34KkvcKj9PHW19WzjT"); + await api.project.getActivity(rid); }); test("#getReadme(id, sha)", async () => { - await api.project.getReadme( - "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", - "fcc929424b82984b7cbff9c01d2e20d9b1249842", - ); + await api.project.getReadme(rid, aliceMainHead); }); test("#getBlob(id, sha, path)", async () => { - await api.project.getBlob( - "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", - "dd068e9aff9a569e597f6abaf84f120dd0cbbd70", - "src/true.c", - ); + await api.project.getBlob(rid, aliceMainHead, "src/true.c"); }); test("#getTree(id, sha)", async () => { - await api.project.getTree( - "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", - "dd068e9aff9a569e597f6abaf84f120dd0cbbd70", - ); + await api.project.getTree(rid, aliceMainHead); }); test("#getTree(id, sha, path)", async () => { - await api.project.getTree( - "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", - "dd068e9aff9a569e597f6abaf84f120dd0cbbd70", - "src", - ); + await api.project.getTree(rid, aliceMainHead, "src"); }); test("#getAllRemotes(id)", async () => { - await api.project.getAllRemotes("rad:zKtT7DmF9H34KkvcKj9PHW19WzjT"); + await api.project.getAllRemotes(rid); }); test("#getRemoteByPeer(id, peer)", async () => { - await api.project.getRemoteByPeer( - "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", - "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi", - ); + await api.project.getRemoteByPeer(rid, aliceRemote.substring(8)); }); test("#getAllCommits(id)", async () => { - await api.project.getAllCommits("rad:zKtT7DmF9H34KkvcKj9PHW19WzjT"); + await api.project.getAllCommits(rid); }); // TODO: test since/until properly. test("#getAllCommits(id, {parent, since, until, page, perPage})", async () => { - await api.project.getAllCommits("rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", { - parent: "f0b8db68847b01f0964380507a9db6800e5b5342", + await api.project.getAllCommits(rid, { + parent: aliceMainHead, since: 1679065819581, until: 1679065819590, page: 1, @@ -83,10 +66,7 @@ describe("project", () => { }); test("#getCommitBySha(id, sha)", async () => { - await api.project.getCommitBySha( - "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", - "fcc929424b82984b7cbff9c01d2e20d9b1249842", - ); + await api.project.getCommitBySha(rid, aliceMainHead); }); test.todo("#getDiff(id, revisionBase, revisionOid)"); diff --git a/httpd-client/vite.config.ts b/httpd-client/vite.config.ts index 3e2cdd370..3b48411a3 100644 --- a/httpd-client/vite.config.ts +++ b/httpd-client/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ resolve: { alias: { "@tests": path.resolve("./tests"), + "@app": path.resolve("./src"), }, }, }); diff --git a/package-lock.json b/package-lock.json index 61ca1ee4c..98562d6b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,17 +39,22 @@ "@types/node": "^18.16.2", "@types/sinon": "^10.0.14", "@types/sinonjs__fake-timers": "^8.1.2", + "@types/wait-on": "^5.3.1", "@typescript-eslint/eslint-plugin": "^5.59.2", "chalk": "^5.2.0", "eslint": "^8.39.0", "eslint-plugin-svelte3": "^4.0.0", + "execa": "^7.1.1", + "exit-hook": "^3.2.0", + "get-port": "^6.1.2", "happy-dom": "^9.10.9", "prettier": "^2.8.8", "prettier-plugin-svelte": "^2.10.0", "svelte-check": "^3.3.0", "typescript": "^5.0.4", "vite": "^4.3.4", - "vitest": "^0.31.0" + "vitest": "^0.31.0", + "wait-on": "^7.0.1" }, "engines": { "node": ">=18.15.0" @@ -432,14 +437,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -455,14 +460,29 @@ } }, "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", + "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -595,6 +615,27 @@ "node": ">=6.0" } }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "node_modules/@sinonjs/commons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", @@ -716,9 +757,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.16.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.4.tgz", - "integrity": "sha512-LUhvPmAKAbgm+p/K11IWszLZVoZDlMF4NRmqbhEzDz/CnCuehPkZXwZbBCKGJsgjnuVejotBwM7B3Scrq4EqDw==", + "version": "18.16.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.8.tgz", + "integrity": "sha512-p0iAXcfWCOTCBbsExHIDFCfwsqFwBTgETJveKMT+Ci3LY9YqQCI91F5S+TB20+aRCXpcWfvx5Qr5EccnwCm2NA==", "dev": true }, "node_modules/@types/parse5": { @@ -733,9 +774,9 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, "node_modules/@types/sinon": { @@ -764,16 +805,25 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, + "node_modules/@types/wait-on": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.1.tgz", + "integrity": "sha512-2FFOKCF/YydrMUaqg+fkk49qf0e5rDgwt6aQsMzFQzbS419h2gNOXyiwp/o2yYy27bi/C1z+HgfncryjGzlvgQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz", - "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz", + "integrity": "sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/type-utils": "5.59.2", - "@typescript-eslint/utils": "5.59.2", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/type-utils": "5.59.5", + "@typescript-eslint/utils": "5.59.5", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -799,15 +849,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz", - "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz", + "integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.5", "debug": "^4.3.4" }, "engines": { @@ -827,13 +877,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", - "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz", + "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2" + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -844,13 +894,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", - "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz", + "integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.2", - "@typescript-eslint/utils": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.5", + "@typescript-eslint/utils": "5.59.5", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -871,9 +921,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", - "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz", + "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -884,13 +934,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", - "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz", + "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/visitor-keys": "5.59.2", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -911,17 +961,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", - "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz", + "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.2", - "@typescript-eslint/types": "5.59.2", - "@typescript-eslint/typescript-estree": "5.59.2", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.5", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -937,12 +987,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", - "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz", + "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/types": "5.59.5", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -1167,6 +1217,22 @@ "node": "*" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/baconjs": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/baconjs/-/baconjs-3.0.17.tgz", @@ -1428,6 +1494,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1554,6 +1632,15 @@ "node": ">=0.10.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -1596,9 +1683,21 @@ } }, "node_modules/dompurify": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.2.tgz", - "integrity": "sha512-B8c6JdiEpxAKnd8Dm++QQxJL4lfuc757scZtcapj6qjTjrQzyq5iAyznLKVvK+77eYNiFblHBlt7MM0fOeqoKw==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz", + "integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==" + }, + "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/es6-promise": { "version": "3.3.1", @@ -1656,15 +1755,15 @@ } }, "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1675,8 +1774,8 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1736,9 +1835,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1804,14 +1903,14 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1880,6 +1979,41 @@ "node": ">=0.10.0" } }, + "node_modules/execa": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-hook": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-3.2.0.tgz", + "integrity": "sha512-aIQN7Q04HGAV/I5BszisuHTZHXNoC23WtLkxdCLuYZMdWviRD0TMIt2bnUBi9MrHaF/hH8b3gwG9iaAUHKnJGA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -2011,6 +2145,40 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -2061,6 +2229,30 @@ "node": "*" } }, + "node_modules/get-port": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-6.1.2.tgz", + "integrity": "sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2140,13 +2332,13 @@ "dev": true }, "node_modules/happy-dom": { - "version": "9.10.9", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-9.10.9.tgz", - "integrity": "sha512-3RnOyu6buPMpDAyOpp8yfR5Xi/k2p5MhrDwlG/dgpVHkptFN5IqubdbGOQU5luB7ANh6a08GOuiB+Bo9JCzCBw==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-9.16.0.tgz", + "integrity": "sha512-goq7grRjIiV2Svb251LWQOo/xm04za2mJ9+assbZJx1KnaVOX1gZBBp4MHbiFNkR6JW7UL81iCtZxCVu+qU5ng==", "dev": true, "dependencies": { "css.escape": "^1.5.1", - "he": "^1.2.0", + "entities": "^4.5.0", "iconv-lite": "^0.6.3", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", @@ -2290,15 +2482,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/html-void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", @@ -2308,6 +2491,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -2462,6 +2654,18 @@ "node": ">=8" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -2473,6 +2677,19 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/joi": { + "version": "17.9.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz", + "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-sdsl": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", @@ -2659,9 +2876,9 @@ } }, "node_modules/marked": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-5.0.1.tgz", - "integrity": "sha512-Nn9peC4lvIZdcfp8Uze6xk4ZYowkcj/K6+e/6rLHadhtjqeip/bYRxMgt3124IGGJ3RPs2uX5YVmAGbUutY18g==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-TXksm9GwqXCRNbFUZmMtqNLvy3K2cQHuWmyBDLOrY1e6i9UvZpOTJXoz7fBjYkJkaUFzV9hBFxMuZSyQt8R6KQ==", "bin": { "marked": "bin/marked.js" }, @@ -2691,6 +2908,12 @@ "node": ">=8" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2713,6 +2936,39 @@ "node": ">=8.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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -2756,15 +3012,15 @@ } }, "node_modules/mlly": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.2.0.tgz", - "integrity": "sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.2.1.tgz", + "integrity": "sha512-1aMEByaWgBPEbWV2BOPEMySRrzl7rIHXmQxam4DM8jVjalTQDjpN2ZKOLUrwyhfZQO7IXHml2StcHMhooDeEEQ==", "dev": true, "dependencies": { "acorn": "^8.8.2", "pathe": "^1.1.0", - "pkg-types": "^1.0.2", - "ufo": "^1.1.1" + "pkg-types": "^1.0.3", + "ufo": "^1.1.2" } }, "node_modules/mri": { @@ -2833,6 +3089,33 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2842,6 +3125,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -3181,9 +3479,9 @@ } }, "node_modules/rollup": { - "version": "3.21.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.5.tgz", - "integrity": "sha512-a4NTKS4u9PusbUJcfF4IMxuqjFzjm6ifj76P54a7cKnvVzJaG12BLVR+hgU2YDGHzyMMQNxLAZWuALsn8q2oQg==", + "version": "3.21.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.6.tgz", + "integrity": "sha512-SXIICxvxQxR3D4dp/3LDHZIJPC8a4anKMHd4E3Jiz2/JnY+2bEjqrOokAauc5ShGVNFHlEFjBXAXlaxkJqIqSg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -3219,6 +3517,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -3315,6 +3628,12 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/sinon": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.4.tgz", @@ -3427,6 +3746,18 @@ "node": ">=0.10.0" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -3475,17 +3806,17 @@ } }, "node_modules/svelte": { - "version": "3.58.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.58.0.tgz", - "integrity": "sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==", + "version": "3.59.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz", + "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==", "engines": { "node": ">= 8" } }, "node_modules/svelte-check": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.3.0.tgz", - "integrity": "sha512-wZtOvY8V2fjzCbS4dGjDp0Ebh6VyXg6A39s7TDc8wc0154yqWKu18Rd9Ad+GOs7sXst7dbTmjvOaexjwoqPM7A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.3.2.tgz", + "integrity": "sha512-67j3rI0LDc2DvL0ON/2pvCasVVD3nHDrTkZNr4eITNfo2oFXdw7SIyMOiFj4swu+pjmFQAigytBK1IWyik8dBw==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -3873,9 +4204,9 @@ } }, "node_modules/vite": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.4.tgz", - "integrity": "sha512-f90aqGBoxSFxWph2b39ae2uHAxm5jFBBdnfueNxZAT1FTpM13ccFQExCaKbR2xFW5atowjleRniQ7onjJ22QEg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.5.tgz", + "integrity": "sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==", "dev": true, "dependencies": { "esbuild": "^0.17.5", @@ -4045,6 +4376,25 @@ "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.0.0.tgz", "integrity": "sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==" }, + "node_modules/wait-on": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", + "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "dev": true, + "dependencies": { + "axios": "^0.27.2", + "joi": "^17.7.0", + "lodash": "^4.17.21", + "minimist": "^1.2.7", + "rxjs": "^7.8.0" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", diff --git a/package.json b/package.json index 4f571f55f..75efcaaf5 100644 --- a/package.json +++ b/package.json @@ -32,16 +32,21 @@ "@types/sinon": "^10.0.14", "@types/sinonjs__fake-timers": "^8.1.2", "@typescript-eslint/eslint-plugin": "^5.59.2", + "@types/wait-on": "^5.3.1", "chalk": "^5.2.0", "eslint": "^8.39.0", "eslint-plugin-svelte3": "^4.0.0", "happy-dom": "^9.10.9", "prettier": "^2.8.8", + "execa": "^7.1.1", + "exit-hook": "^3.2.0", + "get-port": "^6.1.2", "prettier-plugin-svelte": "^2.10.0", "svelte-check": "^3.3.0", "typescript": "^5.0.4", "vite": "^4.3.4", - "vitest": "^0.31.0" + "vitest": "^0.31.0", + "wait-on": "^7.0.1" }, "dependencies": { "@radicle/gray-matter": "4.1.0", diff --git a/scripts/create-seed-fixture b/scripts/create-seed-fixture deleted file mode 100755 index 7a28f07cb..000000000 --- a/scripts/create-seed-fixture +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env bash - -killall radicle-node -killall git-daemon - -set -euo pipefail - -export RAD_PASSPHRASE=asdf - -REPO_ROOT=$(git rev-parse --show-toplevel) -ID=$(echo $RANDOM | md5sum | head -c 4) -BASE_PATH=$REPO_ROOT/tests/tmp/seed-fixture-$ID - -TEST_REPO_ARCHIVE=$REPO_ROOT/tests/fixtures/repos/source-browsing.tar.bz2 -TEST_REPO_NAME=source-browsing -TEST_REPO_PATH=$BASE_PATH/repos/$TEST_REPO_NAME - -PALM_RAD_HOME=$BASE_PATH/seeds/palm -PALM_CHECKOUT=$BASE_PATH/checkout/palm -ALICE_RAD_HOME=$BASE_PATH/peers/alice -ALICE_CHECKOUT=$BASE_PATH/checkout/alice -BOB_RAD_HOME=$BASE_PATH/peers/bob -BOB_CHECKOUT=$BASE_PATH/checkout/bob - -mkdir -p $PALM_RAD_HOME -mkdir -p $PALM_CHECKOUT -mkdir -p $ALICE_RAD_HOME -mkdir -p $ALICE_CHECKOUT -mkdir -p $BOB_RAD_HOME -mkdir -p $BOB_CHECKOUT -mkdir -p $TEST_REPO_PATH - -tar -xf $TEST_REPO_ARCHIVE -C $TEST_REPO_PATH - -### PALM NODE ### - -eval $(ssh-agent) - -export RAD_HOME=$PALM_RAD_HOME -export RAD_SEED=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee - -rad auth -radicle-node --listen 0.0.0.0:3446 --git-daemon 0.0.0.0:4446 --tracking-policy track --tracking-scope all & - -### ALICE ### - -export RAD_HOME=$ALICE_RAD_HOME -export RAD_SEED=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - -export GIT_AUTHOR_NAME="Alice Liddell" -export GIT_AUTHOR_EMAIL="alice@radicle.xyz" -export GIT_COMMITTER_NAME="Alice Liddell" -export GIT_COMMITTER_EMAIL="alice@radicle.xyz" - -rad auth - -cd $ALICE_CHECKOUT - -git clone $TEST_REPO_PATH -cd $TEST_REPO_NAME - -git checkout main -rad init --name "source-browsing" \ - --description "Git repository for source browsing tests" \ - --default-branch "main" \ - --no-confirm - -git checkout feature/branch -git push rad - -git checkout orphaned-branch -git push rad - -radicle-node --listen 0.0.0.0:3444 --git-daemon 0.0.0.0:4444 \ - --connect z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8@0.0.0.0:3446 & - -sleep 1 - -### PALM CLONE ### - -cd $PALM_CHECKOUT -export RAD_HOME=$PALM_RAD_HOME -rad clone rad:zKtT7DmF9H34KkvcKj9PHW19WzjT - -### BOB ### - -export GIT_AUTHOR_NAME="Bob Belcher" -export GIT_AUTHOR_EMAIL="bob@radicle.xyz" -export GIT_COMMITTER_NAME="Bob Belcher" -export GIT_COMMITTER_EMAIL="bob@radicle.xyz" -export GIT_COMMITTER_DATE="Mon Dec 21 14:00 2022 +0100" - -export RAD_HOME=$BOB_RAD_HOME -export RAD_SEED=fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe - -rad auth -rad auth -radicle-node --listen 0.0.0.0:3445 --git-daemon 0.0.0.0:4445 \ - --connect z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8@0.0.0.0:3446 & - -sleep 2 - -cd $BOB_CHECKOUT -rad clone rad:zKtT7DmF9H34KkvcKj9PHW19WzjT - -cd $TEST_REPO_NAME -echo "Updated readme" > README.md -git add README.md -git commit --message "Update readme" --date "$GIT_COMMITTER_DATE" -git push rad - -### WAIT FOR SYNC WITH PALM ### - -sleep 2 - -cd $BASE_PATH -tar -cjf palm.tar.bz2 --exclude "post-receive" --exclude "pre-receive" -C $PALM_RAD_HOME . - -killall radicle-node -killall git-daemon diff --git a/scripts/install-binaries b/scripts/install-binaries new file mode 100755 index 000000000..7cdfafb2c --- /dev/null +++ b/scripts/install-binaries @@ -0,0 +1,90 @@ +#!/bin/bash +set -e + +BINARIES=(rad radicle-node radicle-httpd git-remote-rad) +REPO_ROOT=$(git rev-parse --show-toplevel) +REV=$(cat "$REPO_ROOT/tests/support/heartwood-version") +BINARY_PATH=$REPO_ROOT/tests/tmp/bin/${REV:0:7} +OS=$(uname) + +show_usage() { + echo + echo "Installs binaries required for running e2e test suite." + echo + echo "USAGE:" + echo " install-binaries [-s|h]" + echo + echo "OPTIONS:" + echo " -s --show-path Print the binary path, and skip installation." + echo " -h --help Print this Help." + echo +} + +while [ $# -ne 0 ]; do + case "$1" in + --show-path | -s) + echo "$BINARY_PATH" + exit + ;; + --help | -h) + show_usage + exit + ;; + esac + +done + + +echo +echo "Using revision $REV" +echo + +mkdir -p "$BINARY_PATH" + +for BINARY_NAME in "${BINARIES[@]}" +do + if [ -x "$(command -v "$BINARY_PATH/$BINARY_NAME")" ]; then + echo ✅ "$BINARY_NAME" + else + # To provide deterministic Node and Repo IDs, we need a rad CLI compiled with the --debug flag. + if [ "$BINARY_NAME" = "rad" ]; then + DOWNLOAD_NAME=rad-debug + else + DOWNLOAD_NAME=$BINARY_NAME + fi + + case "$OS" in + Darwin) + echo Downloading "$BINARY_NAME" from "https://files.radicle.xyz/$REV/aarch64-apple-darwin/$DOWNLOAD_NAME" + curl --fail -s "https://files.radicle.xyz/$REV/aarch64-apple-darwin/$DOWNLOAD_NAME" --output "$BINARY_PATH/$BINARY_NAME" || (echo "Download failed" && exit 1);; + Linux) + echo Downloading "$BINARY_NAME" from "https://files.radicle.xyz/$REV/x86_64-unknown-linux-musl/$DOWNLOAD_NAME" + curl --fail -s "https://files.radicle.xyz/$REV/x86_64-unknown-linux-musl/$DOWNLOAD_NAME" --output "$BINARY_PATH/$BINARY_NAME" || (echo "Download failed" && exit 1);; + *) echo "There are no precompiled binaries for your OS: $OS, compile $BINARY_NAME manually and make sure it's in PATH." && exit 1 ;; + esac + + chmod a+x "$BINARY_PATH/$BINARY_NAME" + fi +done + +RADICLE_HOME=${RAD_HOME:-"${HOME}/.radicle/bin"} + +# Add a separator between binaries download and PATH instructions. +echo + +if [[ ":$PATH:" != *":$BINARY_PATH:"* ]]; then + if [[ ":$PATH:" == *":$RADICLE_HOME:"* ]]; then + echo "⚠️ You already have a radicle bin folder in your PATH variable." + echo + echo "To ensure you are using the correct test binaries, run the following command:" + else + echo "Before running the tests, make sure your PATH variable is set up correctly by running:" + fi + + echo " 👉 export PATH=$BINARY_PATH:\$PATH" + +else + echo "✅ $BINARY_PATH is already in your PATH variable." +fi + + diff --git a/scripts/run-httpd-with-fixtures b/scripts/run-httpd-with-fixtures deleted file mode 100755 index 25022c16d..000000000 --- a/scripts/run-httpd-with-fixtures +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -set -e - -REV=b29321dbf7999ec7ec9b7ac9192071e512ada407 - -REPO_ROOT=$(git rev-parse --show-toplevel) -FIXTURE=$REPO_ROOT/tests/fixtures/seeds/palm.tar.bz2 -WORKSPACE=$REPO_ROOT/tests/tmp/palm -PASSPHRASE=asdf -BINARY_PATH="$REPO_ROOT/tests/tmp" -BINARY_NAME=radicle-httpd -OS=$(uname) - -show_usage() { - echo - echo "Starts a ${BINARY_NAME} backend with test fixtures." - echo - echo "USAGE:" - echo " run-httpd-with-fixtures [-d|h|n]" - echo - echo "OPTIONS:" - echo " -d --download Download and use a precompiled binary." - echo " -h --help Print this Help." - echo " -n --non-interactive Run in non-interactive mode, no user prompts." - echo -} - -prompt_workspace_removal() { - echo "This will irrevocably destroy the following directories:" - echo - echo $WORKSPACE - echo - - read -r -p "Are you sure you want to continue? [yes/no]: " confirm - case "$confirm" in - [yY][eE][sS]) - rm -rf $WORKSPACE - echo "Done" - ;; - *) - echo "Ok, I won't touch your data." - exit - ;; - esac -} - -prepare_workspace() { - echo - echo "Unpacking fixture $FIXTURE" - mkdir -p $WORKSPACE - tar -xf $FIXTURE -C $WORKSPACE -} - -NON_INTERACTIVE=false -DOWNLOAD=false - -while [ $# -ne 0 ]; do - case "$1" in - --download | -d) - DOWNLOAD=true - ;; - --non-interactive | -n) - NON_INTERACTIVE=true - ;; - *) - show_usage - exit - ;; - esac - - shift -done - -if [ "$DOWNLOAD" = true ]; then - CACHED_BINARY_NAME="$BINARY_NAME-${REV:0:7}" - - if ! [ -x "$(command -v $BINARY_PATH/$CACHED_BINARY_NAME)" ]; then - case "$OS" in - Darwin) - echo Downloading $BINARY_NAME from https://files.radicle.xyz/$REV/aarch64-apple-darwin/$BINARY_NAME - curl --fail -s "https://files.radicle.xyz/$REV/aarch64-apple-darwin/$BINARY_NAME" --output "$BINARY_PATH/$CACHED_BINARY_NAME" || (echo "Download failed" && exit 1);; - Linux) - echo Downloading $BINARY_NAME from https://files.radicle.xyz/$REV/x86_64-unknown-linux-musl/$BINARY_NAME - curl --fail -s "https://files.radicle.xyz/$REV/x86_64-unknown-linux-musl/$BINARY_NAME" --output "$BINARY_PATH/$CACHED_BINARY_NAME" || (echo "Download failed" && exit 1);; - *) echo "There are no precompiled binaries for your OS: $OS, compile $BINARY_NAME manually and make sure it's in PATH." && exit 1 ;; - esac - - chmod a+x "$BINARY_PATH/$CACHED_BINARY_NAME" - fi - - export PATH="$BINARY_PATH:$PATH" - BINARY_NAME=$CACHED_BINARY_NAME -fi - -if ! [ -x "$(command -v $BINARY_NAME)" ]; then - echo - echo "Couldn't find $BINARY_NAME binary in your PATH." - echo - echo "You can download it with the --download option, or" - echo "compile it from source:" - echo - echo " 👉 https://github.com/radicle-dev/heartwood" - echo - exit 1 -fi - -if [ "$NON_INTERACTIVE" = true ]; then - rm -rf $WORKSPACE - prepare_workspace -else - prompt_workspace_removal - prepare_workspace -fi - -echo -echo "Starting $BINARY_NAME" -echo " RAD_HOME=$WORKSPACE RAD_PASSPHRASE=$PASSPHRASE $BINARY_NAME" -echo - -RAD_HOME=$WORKSPACE RAD_PASSPHRASE=$PASSPHRASE $BINARY_NAME diff --git a/tests/e2e/clipboard.spec.ts b/tests/e2e/clipboard.spec.ts index e69710c94..06455c32b 100644 --- a/tests/e2e/clipboard.spec.ts +++ b/tests/e2e/clipboard.spec.ts @@ -1,8 +1,10 @@ import type { Page } from "@playwright/test"; + import { expect, projectFixtureUrl, rid, + seedRemote, test, } from "@tests/support/fixtures.js"; @@ -35,7 +37,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => { const clipboardContent = await page.evaluate( "navigator.clipboard.readText()", ); - expect(clipboardContent).toBe(`${rid}`); + expect(clipboardContent).toBe(rid); } // `rad clone` URL. @@ -78,10 +80,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => { // Seed address. { await page.locator(".clipboard").first().click(); - await expectClipboard( - "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8@127.0.0.1:8776", - page, - ); + await expectClipboard(`${seedRemote}@127.0.0.1:8776`, page); } // Clear the system clipboard contents so developers don't wonder why there's diff --git a/tests/e2e/project.spec.ts b/tests/e2e/project.spec.ts index 3a500e732..98b97b025 100644 --- a/tests/e2e/project.spec.ts +++ b/tests/e2e/project.spec.ts @@ -1,4 +1,5 @@ import type { Page } from "@playwright/test"; + import { aliceMainHead, aliceRemote, @@ -341,10 +342,10 @@ test("peer and branch switching", async ({ page }) => { // Default `main` branch. { await expect(page.getByTitle("Current branch")).toContainText( - "main 1e0bb83", + "main ec5eb0b", ); await expectCounts({ commits: 9, contributors: 2 }, page); - await expect(page.locator("text=1e0bb83 Update readme")).toBeVisible(); + await expect(page.locator("text=ec5eb0b Update readme")).toBeVisible(); } } }); diff --git a/tests/e2e/project/commit.spec.ts b/tests/e2e/project/commit.spec.ts index a0063a41b..2639bbc9f 100644 --- a/tests/e2e/project/commit.spec.ts +++ b/tests/e2e/project/commit.spec.ts @@ -3,11 +3,12 @@ import { expect, projectFixtureUrl, bobRemote, + bobHead, } from "@tests/support/fixtures.js"; const modifiedFileFixture = `${projectFixtureUrl}/remotes/${bobRemote.substring( 8, -)}/commits/1e0bb83a89b63da815f2fc24e7ae3c5ceb30e0eb`; +)}/commits/${bobHead}`; test("navigation from commit list", async ({ page }) => { await page.goto(projectFixtureUrl); @@ -43,7 +44,7 @@ test("modified file", async ({ page }) => { const header = page.locator(".commit .header"); await expect(header.locator("text=Update readme")).toBeVisible(); await expect( - header.locator("text=1e0bb83a89b63da815f2fc24e7ae3c5ceb30e0eb"), + header.locator("text=ec5eb0b5efb73da17a2d25454cc47eea3967f328"), ).toBeVisible(); } diff --git a/tests/e2e/project/commits.spec.ts b/tests/e2e/project/commits.spec.ts index e077eee4b..8bf15cd56 100644 --- a/tests/e2e/project/commits.spec.ts +++ b/tests/e2e/project/commits.spec.ts @@ -76,7 +76,7 @@ test("peer and branch switching", async ({ page }) => { const latestCommit = page.locator(".teaser").first(); await expect(latestCommit).toContainText("Update readme"); - await expect(latestCommit).toContainText("1e0bb83"); + await expect(latestCommit).toContainText("ec5eb0b"); const earliestCommit = page.locator(".teaser").last(); await expect(earliestCommit).toContainText( @@ -109,7 +109,7 @@ test("relative timestamps", async ({ page }) => { ); const latestCommit = page.locator(".teaser").first(); await expect(latestCommit).toContainText("Bob Belcher committed now"); - await expect(latestCommit).toContainText("1e0bb83"); + await expect(latestCommit).toContainText("ec5eb0b"); const earliestCommit = page.locator(".teaser").last(); await expect(earliestCommit).toContainText( "Alice Liddell committed last month", diff --git a/tests/fixtures/seeds/palm.tar.bz2 b/tests/fixtures/seeds/palm.tar.bz2 deleted file mode 100644 index 78c25d57f..000000000 Binary files a/tests/fixtures/seeds/palm.tar.bz2 and /dev/null differ diff --git a/tests/support/fixtures.ts b/tests/support/fixtures.ts index f3d998e7f..f05876c00 100644 --- a/tests/support/fixtures.ts +++ b/tests/support/fixtures.ts @@ -1,13 +1,25 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import type * as Stream from "node:stream"; import * as Fs from "node:fs/promises"; +import * as FsSync from "node:fs"; import * as Path from "node:path"; +import { dirname } from "path"; +import { fileURLToPath } from "url"; import { test as base, expect } from "@playwright/test"; +import * as Process from "./process.js"; import * as logLabel from "@tests/support/logLabel.js"; +import { createPeerManager } from "./peerManager.js"; +import { sleep } from "@app/lib/sleep"; export { expect }; +const filename = fileURLToPath(import.meta.url); +export const supportDir = dirname(filename); +export const tmpDir = Path.resolve(supportDir, "..", "./tmp"); +const fixturesDir = Path.resolve(supportDir, "..", "./fixtures"); + export const test = base.extend<{ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type forAllTests: void; @@ -135,39 +147,6 @@ function log(text: string, label: string, outputLog: Stream.Writable) { } } -export function configFixture() { - window.APP_CONFIG = { - reactions: [], - seeds: { - defaultHttpdPort: 8080, - defaultHttpdScheme: "http", - defaultNodePort: 8776, - pinned: [ - { - baseUrl: { - hostname: "127.0.0.1", - port: 8080, - scheme: "http", - }, - }, - ], - }, - projects: { - pinned: [ - { - name: "source-browsing", - id: "rad:git:hnrkdi8be7n4hhqoz9rpzrgd68u9dr3zsxgmy", - baseUrl: { - hostname: "127.0.0.1", - port: 8080, - scheme: "http", - }, - }, - ], - }, - }; -} - export function appConfigWithFixture() { window.APP_CONFIG = { reactions: [], @@ -189,7 +168,7 @@ export function appConfigWithFixture() { pinned: [ { name: "source-browsing", - id: "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT", + id: "rad:z4BwwjPCFNVP27FwVbDFgwVwkjcir", baseUrl: { hostname: "127.0.0.1", port: 8080, @@ -201,12 +180,101 @@ export function appConfigWithFixture() { }; } +export async function startPalmHttpd() { + const peerManager = await createPeerManager({ + dataDir: Path.resolve(Path.join(tmpDir, "peers")), + outputLog: FsSync.createWriteStream(Path.resolve(Path.join(tmpDir, "log"))), + }); + const palm = await peerManager.startPeer({ name: "palm" }); + await palm.startHttpd(); +} + +export async function createSeedFixture() { + const projectName = "source-browsing"; + const sourceBrowsingDir = Path.join(tmpDir, "repos", projectName); + await Fs.mkdir(sourceBrowsingDir, { recursive: true }); + await Process.spawn("tar", [ + "-xf", + Path.join(fixturesDir, `repos/${projectName}.tar.bz2`), + "-C", + sourceBrowsingDir, + ]); + const peerManager = await createPeerManager({ + dataDir: Path.resolve(Path.join(tmpDir, "peers")), + outputLog: FsSync.createWriteStream(Path.join(tmpDir, "log")), + }); + const palm = await peerManager.startPeer({ name: "palm" }); + await palm.startHttpd(); + const alice = await peerManager.startPeer({ + name: "alice", + gitOptions: gitOptions["alice"], + }); + const bob = await peerManager.startPeer({ + name: "bob", + gitOptions: gitOptions["bob"], + }); + await palm.startNode({ trackingPolicy: "track", trackingScope: "all" }); + await alice.startNode({ connect: palm }); + await bob.startNode({ connect: palm }); + await sleep(1000); + await alice.git(["clone", sourceBrowsingDir], { cwd: alice.checkoutPath }); + await alice.git(["checkout", "main"]); + await alice.rad([ + "init", + "--name", + projectName, + "--default-branch", + "main", + "--description", + "Git repository for source browsing tests", + "--announce", + ]); + await alice.git(["checkout", "feature/branch"]); + await alice.git(["push", "rad"]); + await alice.git(["checkout", "orphaned-branch"]); + await alice.git(["push", "rad"]); + const { stdout: rid } = await alice.rad(["inspect"]); + await alice.rad(["track", bob.nodeId]); + await sleep(2000); + await bob.rad(["clone", rid], { cwd: bob.checkoutPath }); + await sleep(2000); + await Fs.writeFile( + Path.join(bob.checkoutPath, "source-browsing", "README.md"), + "Updated readme", + ); + await bob.git(["add", "README.md"]); + await bob.git([ + "commit", + "--message", + "Update readme", + "--date", + "Mon Dec 21 14:00 2022 +0100", + ]); + await bob.git(["push", "rad"]); +} + export const aliceMainHead = "fcc929424b82984b7cbff9c01d2e20d9b1249842"; export const aliceRemote = - "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"; + "did:key:z6MkqGC3nWZhYieEVTVDKW5v588CiGfsDSmRVG9ZwwWTvLSK"; export const bobRemote = - "did:key:z6MksMTThc1aDU2Ztc43jJUivuyBLNWiLsDf4X65rABe7HbA"; -export const rid = "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT"; + "did:key:z6Mkg49NtQR2LyYRDCQFK4w1VVHqhypZSSRo7HsyuN7SV7v5"; +export const bobHead = "ec5eb0b5efb73da17a2d25454cc47eea3967f328"; +export const rid = "rad:z4BwwjPCFNVP27FwVbDFgwVwkjcir"; export const projectFixtureUrl = `/seeds/127.0.0.1/${rid}`; export const seedPort = 8080; -export const seedRemote = "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"; +export const seedRemote = "z6MktULudTtAsAhRegYPiZ6631RV3viv12qd4GQF8z1xB22S"; +export const gitOptions = { + alice: { + GIT_AUTHOR_NAME: "Alice Liddell", + GIT_AUTHOR_EMAIL: "alice@radicle.xyz", + GIT_COMMITTER_NAME: "Alice Liddell", + GIT_COMMITTER_EMAIL: "alice@radicle.xyz", + }, + bob: { + GIT_AUTHOR_NAME: "Bob Belcher", + GIT_AUTHOR_EMAIL: "bob@radicle.xyz", + GIT_COMMITTER_NAME: "Bob Belcher", + GIT_COMMITTER_EMAIL: "bob@radicle.xyz", + GIT_COMMITTER_DATE: "Mon Dec 21 14:00 2022 +0100", + }, +}; diff --git a/tests/support/globalSetup.ts b/tests/support/globalSetup.ts index de9dd8a91..f37fade5e 100644 --- a/tests/support/globalSetup.ts +++ b/tests/support/globalSetup.ts @@ -1,31 +1,86 @@ import type { FullConfig } from "@playwright/test"; -import { seedPort, seedRemote } from "@tests/support/fixtures.js"; +import * as Fs from "node:fs/promises"; +import * as Path from "node:path"; +import * as readline from "node:readline/promises"; +import { execa } from "execa"; + +import { + createSeedFixture, + supportDir, + startPalmHttpd, + tmpDir, +} from "./fixtures.js"; + +const workspacePaths = [Path.join(tmpDir, "peers"), Path.join(tmpDir, "repos")]; export default async function globalSetup(_config: FullConfig): Promise { - await assertHttpApiRunning(); -} - -// Assert that the test http-api is running. If it is not running, throw an -// error that explains how to run it. -async function assertHttpApiRunning(): Promise { - const notRunningMessage = - "The http-api server with test fixtures needs to be running.\n" + - "👉 You can start it with `./scripts/run-httpd-with-fixtures`\n"; - - let nodeId: string | undefined = undefined; - try { - const response = await fetch(`http://0.0.0.0:${seedPort}/api/v1`); - const data = await response.json(); - nodeId = data.node.id; - } catch (err) { - console.error(err); - throw new Error(notRunningMessage); + await assertRadInstalled(); + } catch (error) { + console.error(error); + process.exit(1); } - if (nodeId !== seedRemote) { - const wrongSeedMessage = `The server on port ${seedPort} doesn't have the right fixtures.\n`; - throw new Error(wrongSeedMessage + notRunningMessage); + if (!process.env.SKIP_FIXTURE_CREATION) { + console.log("Setting up global test environment"); + if (!process.env.CI) { + await promptWorkspaceRemoval(); + } + await removeWorkspace(); + + console.log("Creating seed fixture"); + await createSeedFixture(); + console.log("Running tests"); + } else { + await startPalmHttpd(); + } +} + +// Assert that the `rad` CLI is installed and has the correct version. +async function assertRadInstalled(): Promise { + const versionConstraint = ( + await Fs.readFile(`${supportDir}/heartwood-version`, "utf8") + ).substring(0, 7); + const { stdout: version } = await execa("rad", ["--version"]); + if (!version.includes(versionConstraint)) { + throw new Error( + `rad version ${version} does not satisfy ${versionConstraint}`, + ); + } +} + +async function promptWorkspaceRemoval(): Promise { + console.log(""); + console.log("This will irrevocably destroy the following directories:"); + console.log(""); + workspacePaths.forEach(path => console.log(path)); + console.log(""); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + const result = await rl.question( + "Are you sure you want to continue? [yes/no]: ", + ); + rl.close(); + + if (result.toLowerCase() === "yes") { + console.log("Done"); + return; + } + + console.log("Ok, I won't touch your data."); + process.exit(1); +} + +async function removeWorkspace(): Promise { + for (const path of workspacePaths) { + await Fs.rm(path, { + recursive: true, + force: true, + }); } } diff --git a/tests/support/heartwood-version b/tests/support/heartwood-version new file mode 100644 index 000000000..34eec893d --- /dev/null +++ b/tests/support/heartwood-version @@ -0,0 +1 @@ +a6a3290833f6da4a7785a426bed342df069ce47f diff --git a/tests/support/peerManager.ts b/tests/support/peerManager.ts new file mode 100644 index 000000000..e03539e0c --- /dev/null +++ b/tests/support/peerManager.ts @@ -0,0 +1,217 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { ExecaChildProcess, Options } from "execa"; + +import * as Fs from "node:fs/promises"; +import * as Path from "node:path"; +import * as Stream from "node:stream"; +import getPort from "get-port"; +import waitOn from "wait-on"; +import { execa } from "execa"; + +import * as Process from "./process.js"; + +interface PeerManagerParams { + dataPath: string; + seed: string; + name: string; + gitOptions?: Record; + outputLog: Stream.Writable; +} + +export interface PeerManager { + startPeer(params: { + name: string; + gitOptions?: Record; + }): Promise; +} + +export function generateSeed(index: number) { + return Array(64).fill(index.toString()).join(""); +} + +export async function createPeerManager(createParams: { + dataDir: string; + outputLog?: Stream.Writable; +}): Promise { + let outputLog: Stream.Writable; + let outputLogFile: Fs.FileHandle; + if (createParams.outputLog) { + outputLog = createParams.outputLog; + } else { + outputLogFile = await Fs.open( + Path.join(createParams.dataDir, "peer-manager.log"), + "a", + ); + outputLog = outputLogFile.createWriteStream(); + } + + const nodes: RadiclePeer[] = []; + return { + // Starts a new node and registers it. + async startPeer(params) { + const peer = await RadiclePeer.create({ + dataPath: createParams.dataDir, + name: params.name, + gitOptions: params.gitOptions, + seed: generateSeed(nodes.length + 1), + outputLog, + }); + nodes.push(peer); + + return peer; + }, + }; +} + +export class RadiclePeer { + public checkoutPath: string; + public nodeId: string; + + #seed: string; + #radHome: string; + #outputLog: Stream.Writable; + #gitOptions?: Record; + #listenSocketAddr?: string; + + private constructor(props: { + checkoutPath: string; + nodeId: string; + seed: string; + gitOptions?: Record; + radHome: string; + logFile: Stream.Writable; + }) { + this.checkoutPath = props.checkoutPath; + this.nodeId = props.nodeId; + this.#gitOptions = props.gitOptions; + this.#seed = props.seed; + this.#radHome = props.radHome; + this.#outputLog = props.logFile; + } + + public static async create({ + dataPath, + name, + gitOptions, + seed, + outputLog: logFile, + }: PeerManagerParams): Promise { + const checkoutPath = Path.join(dataPath, name, "copy"); + await Fs.mkdir(checkoutPath, { recursive: true }); + const radHome = Path.join(dataPath, name, "home"); + await Fs.mkdir(radHome, { recursive: true }); + + const env = { + ...gitOptions, + RAD_HOME: radHome, + RAD_PASSPHRASE: "asdf", + RAD_SEED: seed, + }; + + await execa("rad", ["auth"], { env }); + const { stdout: nodeId } = await execa("rad", ["self", "--nid"], { env }); + + return new RadiclePeer({ + checkoutPath, + gitOptions, + seed, + nodeId, + radHome, + logFile, + }); + } + + public async startHttpd() { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.spawn("radicle-httpd"); + + await waitOn({ + resources: ["tcp:127.0.0.1:8080"], + timeout: 7000, + }); + } + + public async startNode(params?: { + connect?: RadiclePeer; + trackingScope?: "trusted" | "all"; + trackingPolicy?: "track" | "block"; + }) { + const gitPort = await getPort(); + const gitSocketAddr = `0.0.0.0:${gitPort}`; + const listenPort = await getPort(); + this.#listenSocketAddr = `0.0.0.0:${listenPort}`; + + const args = [ + "--git-daemon", + gitSocketAddr, + "--listen", + this.#listenSocketAddr, + ]; + if (params?.connect) { + args.push( + "--connect", + `${params.connect.nodeId}@${params.connect.#listenSocketAddr}`, + ); + } + if (params?.trackingScope) { + args.push("--tracking-scope", params.trackingScope); + } + if (params?.trackingPolicy) { + args.push("--tracking-policy", params.trackingPolicy); + } + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.spawn("radicle-node", args); + + await waitOn({ + resources: [`tcp:${this.#listenSocketAddr}`], + timeout: 7000, + }); + } + + public uiUrl(): string { + return `/seeds/127.0.0.1:8080`; + } + public ridUrl(rid: string): string { + return `/seeds/127.0.0.1:8080/${rid}`; + } + + public git(args: string[] = [], opts?: Options): ExecaChildProcess { + return this.spawn("git", args, { + cwd: Path.join(this.checkoutPath, "source-browsing"), + ...opts, + }); + } + + public rad(args: string[] = [], opts?: Options): ExecaChildProcess { + return this.spawn("rad", args, { + cwd: Path.join(this.checkoutPath, "source-browsing"), + ...opts, + }); + } + + public spawn( + cmd: string, + args: string[] = [], + opts?: Options, + ): ExecaChildProcess { + opts = { + ...opts, + env: { + ...opts?.env, + ...this.#gitOptions, + GIT_CONFIG_GLOBAL: "/dev/null", + GIT_CONFIG_NOSYSTEM: "1", + RAD_HOME: this.#radHome, + RAD_PASSPHRASE: "asdf", + RAD_SEED: this.#seed, + }, + }; + const childProcess = Process.spawn(cmd, args, opts); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Process.prefixOutput(childProcess, this.nodeId, this.#outputLog); + + return childProcess; + } +} diff --git a/tests/support/process.ts b/tests/support/process.ts new file mode 100644 index 000000000..197c96bf8 --- /dev/null +++ b/tests/support/process.ts @@ -0,0 +1,83 @@ +import type { ExecaChildProcess, Options } from "execa"; + +import * as Stream from "node:stream"; +import onExit from "exit-hook"; +import { StringDecoder } from "string_decoder"; +import { execa } from "execa"; + +import { make } from "./logLabel.js"; + +// Processes that should be SIGKILLed when the Node process shutsdown. +// We add all proxy and seed instances that we spawn to this list. +const processes: ExecaChildProcess[] = []; + +onExit(killAllProcesses); + +// Kill all processes with SIGKILL +export function killAllProcesses(): void { + for (const process of processes) { + if (process.exitCode === null) { + process.kill("SIGKILL"); + } + } +} + +// Spawn a process with `execa` and register it. +// +// The process will be killed by `killAllProcesses`. +export function spawn( + bin: string, + args: string[], + options?: Options, +): ExecaChildProcess { + const child = execa(bin, args, options); + processes.push(child); + return child; +} + +// Forwards piped `stdout` and `stderr` of a child process to `output` +// and prefixes it with the given label. The prefix is colored. +export function prefixOutput( + childProcess: ExecaChildProcess, + label: string, + output: Stream.Writable, +): ExecaChildProcess { + const pref = make(label); + if (childProcess.stdout) { + const stdoutPrefix = new LinePrefix(pref); + childProcess.stdout.pipe(stdoutPrefix).pipe(output, { end: false }); + } + if (childProcess.stderr) { + const stderrPrefix = new LinePrefix(pref); + childProcess.stderr.pipe(stderrPrefix).pipe(output, { end: false }); + } + + return childProcess; +} + +// A transform that prefixes each line from the source with the given +// string and pushes it to the sink. +class LinePrefix extends Stream.Transform { + private buffer: string = ""; + private stringDecoder = new StringDecoder(); + + public constructor(private prefix: string) { + super(); + } + + public _transform(data: Buffer, _encoding: string, next: () => void): void { + const str = this.buffer + this.stringDecoder.write(data); + const lines = str.split(/\r?\n/); + this.buffer = lines.pop() || ""; + lines.forEach(line => this.push(`${this.prefix}${line}\n`)); + next(); + } + + public _flush(done: () => void): void { + const rest = `${this.buffer}${this.stringDecoder.end()}`; + if (rest) { + this.push(`${this.prefix}${rest}\n`); + } + done(); + } +}