diff --git a/.github/workflows/frontend-build-check.yaml b/.github/workflows/frontend-build-check.yaml index 3c7990cf..33ddf621 100644 --- a/.github/workflows/frontend-build-check.yaml +++ b/.github/workflows/frontend-build-check.yaml @@ -28,6 +28,9 @@ jobs: - name: Install dependencies working-directory: frontend run: yarn install --frozen-lockfile + - name: Unit tests + working-directory: frontend + run: yarn test - name: Localization build working-directory: frontend run: yarn localize:prepare diff --git a/frontend/package.json b/frontend/package.json index 1badfef1..1e91924f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ }, "scripts": { "prepare": "cd .. && husky install frontend/.husky", - "test": "web-test-runner \"src/**/*.test.{ts,js}\" --node-resolve --playwright --browsers chromium", + "test": "web-test-runner \"src/**/*.test.{ts,js}\" --playwright --browsers chromium", "prebuild": "del-cli ./dist", "build": "webpack --config webpack.prod.js", "build-dev": "webpack --mode development", @@ -49,7 +49,7 @@ "@types/sinon": "^10.0.6", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", - "@web/dev-server-esbuild": "^0.2.16", + "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.0.6", "@web/dev-server-rollup": "^0.3.21", "@web/test-runner": "^0.13.22", @@ -65,6 +65,7 @@ "eslint-plugin-lit": "^1.6.1", "eslint-plugin-wc": "^1.3.2", "eslint-webpack-plugin": "^3.1.1", + "glob": "^8.1.0", "html-loader": "^3.0.1", "html-webpack-plugin": "^5.5.0", "husky": "^8.0.3", diff --git a/frontend/src/__mocks__/shoelace.js b/frontend/src/__mocks__/shoelace.js new file mode 100644 index 00000000..135518e6 --- /dev/null +++ b/frontend/src/__mocks__/shoelace.js @@ -0,0 +1,3 @@ +/** + * Use to mock src/shoelace.ts + */ diff --git a/frontend/src/index.test.ts b/frontend/src/index.test.ts index 5e478b57..33e5fa53 100644 --- a/frontend/src/index.test.ts +++ b/frontend/src/index.test.ts @@ -2,10 +2,12 @@ import { spy, stub, mock, restore } from "sinon"; import { fixture, expect } from "@open-wc/testing"; // import { expect } from "@esm-bundle/chai"; +import AuthService from "./utils/AuthService"; import { App } from "./index"; describe("browsertrix-app", () => { beforeEach(() => { + stub(window.sessionStorage, "setItem"); stub(App.prototype, "getUserInfo").callsFake(() => Promise.resolve({ id: "test_id", @@ -34,10 +36,14 @@ describe("browsertrix-app", () => { expect(el).instanceOf(App); }); + // TODO move tests to AuthService it("sets auth state from session storage", async () => { + stub(AuthService.prototype, "startFreshnessCheck"); stub(window.sessionStorage, "getItem").callsFake((key) => { if (key === "btrix.auth") return JSON.stringify({ + headers: "_fake_headers_", + tokenExpiresAt: "_fake_tokenExpiresAt_", username: "test-auth@example.com", }); return null; @@ -45,11 +51,14 @@ describe("browsertrix-app", () => { const el = (await fixture("")) as App; expect(el.authService.authState).to.eql({ + headers: "_fake_headers_", + tokenExpiresAt: "_fake_tokenExpiresAt_", username: "test-auth@example.com", }); }); it("sets user info", async () => { + stub(AuthService.prototype, "startFreshnessCheck"); stub(window.sessionStorage, "getItem").callsFake((key) => { if (key === "btrix.auth") return JSON.stringify({ @@ -65,6 +74,14 @@ describe("browsertrix-app", () => { name: "Test User", isVerified: false, isAdmin: false, + orgs: [ + { + id: "test_org_id", + name: "test org", + role: 10, + email: "test@org.org", + }, + ], }); }); }); diff --git a/frontend/src/utils/AuthService.ts b/frontend/src/utils/AuthService.ts index 919e4462..70df8641 100644 --- a/frontend/src/utils/AuthService.ts +++ b/frontend/src/utils/AuthService.ts @@ -241,7 +241,7 @@ export default class AuthService { this.revoke(); } - private startFreshnessCheck() { + startFreshnessCheck() { window.clearTimeout(this.timerId); this.checkFreshness(); } @@ -256,7 +256,7 @@ export default class AuthService { AuthService.storage.removeItem(); } - private persist(auth: Auth) { + persist(auth: Auth) { this._authState = { username: auth.username, headers: auth.headers, @@ -307,7 +307,7 @@ export default class AuthService { } } - private async refresh(): Promise<{ + async refresh(): Promise<{ headers: Auth["headers"]; tokenExpiresAt: Auth["tokenExpiresAt"]; }> { diff --git a/frontend/web-test-runner.config.mjs b/frontend/web-test-runner.config.mjs index 35f2c1e6..c3ffc757 100644 --- a/frontend/web-test-runner.config.mjs +++ b/frontend/web-test-runner.config.mjs @@ -3,15 +3,26 @@ import { importMapsPlugin } from "@web/dev-server-import-maps"; import commonjsPlugin from "@rollup/plugin-commonjs"; import { fromRollup } from "@web/dev-server-rollup"; import { fileURLToPath } from "url"; +import glob from "glob"; const commonjs = fromRollup(commonjsPlugin); +// Map all css imports to mock file +const cssImports = {}; +glob.sync("./src/**/*.css").forEach((filepath) => { + cssImports[filepath] = fileURLToPath( + new URL("./src/__mocks__/css.js", import.meta.url) + ); +}); + export default { + nodeResolve: true, + rootDir: process.cwd(), plugins: [ esbuildPlugin({ ts: true, - // tsconfig: fileURLToPath(new URL("./tsconfig.json", import.meta.url)), - target: "auto", + tsconfig: fileURLToPath(new URL("./tsconfig.json", import.meta.url)), + target: "esnext", }), commonjs({ include: [ @@ -24,12 +35,23 @@ export default { inject: { importMap: { imports: { - "tailwindcss/tailwind.css": "/src/__mocks__/css.js", - "@shoelace-style/shoelace/dist/themes/light.css": - "/src/__mocks__/css.js", - "@formatjs/intl-displaynames/should-polyfill": - "/src/__mocks__/@formatjs/intl-displaynames/should-polyfill.js", - color: "/src/__mocks__/color.js", + ...cssImports, + "./src/shoelace": fileURLToPath( + new URL("./src/__mocks__/shoelace.js", import.meta.url) + ), + "tailwindcss/tailwind.css": fileURLToPath( + new URL("./src/__mocks__/css.js", import.meta.url) + ), + "@shoelace-style/shoelace/dist/themes/light.css": fileURLToPath( + new URL("./src/__mocks__/css.js", import.meta.url) + ), + // "@formatjs/intl-displaynames/should-polyfill": new URL( + // "./src/__mocks__/@formatjs/intl-displaynames/should-polyfill.js", + // import.meta.url + // ), + color: fileURLToPath( + new URL("./src/__mocks__/color.js", import.meta.url) + ), }, }, }, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e9ea4db2..7acef521 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -49,6 +49,11 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== +"@esbuild/linux-loong64@0.14.54": + version "0.14.54" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" + integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== + "@eslint/eslintrc@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.4.tgz#dfe0ff7ba270848d10c5add0715e04964c034b31" @@ -838,14 +843,14 @@ picomatch "^2.2.2" ws "^7.4.2" -"@web/dev-server-esbuild@^0.2.16": - version "0.2.16" - resolved "https://registry.yarnpkg.com/@web/dev-server-esbuild/-/dev-server-esbuild-0.2.16.tgz#fc3c429f22d89c597fa60231b6a8cacd404f9294" - integrity sha512-a82uKy9vQ4HvfWtjd7hJ3GtaqkL2ofxpEu3a1wIZyXB2dFWPvhRSmLNe/4IPPHe4vj6PVdRpLSFPEA3lXUW5Pw== +"@web/dev-server-esbuild@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@web/dev-server-esbuild/-/dev-server-esbuild-0.3.3.tgz#e82af2e5acec0e645b920400be9601601b3921c5" + integrity sha512-hB9C8X9NsFWUG2XKT3W+Xcw3IZ/VObf4LNbK14BTjApnNyZfV6hVhSlJfvhgOoJ4DxsImfhIB5+gMRKOG9NmBw== dependencies: "@mdn/browser-compat-data" "^4.0.0" - "@web/dev-server-core" "^0.3.17" - esbuild "^0.12.21" + "@web/dev-server-core" "^0.3.19" + esbuild "^0.12 || ^0.13 || ^0.14" parse5 "^6.0.1" ua-parser-js "^1.0.2" @@ -1487,6 +1492,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2406,10 +2418,132 @@ es-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.1.0.tgz#bf56a09b5f1c6aea6ba231b0a636a0f60c410b70" integrity sha512-fJg+1tiyEeS8figV+fPcPpm8WqJEflG3yPU0NOm5xMvrNkuiy7HzX/Ljng4Y0hAoiw4/3hQTCFYw+ub8+a2pRA== -esbuild@^0.12.21: - version "0.12.29" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.29.tgz#be602db7c4dc78944a9dbde0d1ea19d36c1f882d" - integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g== +esbuild-android-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" + integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== + +esbuild-android-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" + integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== + +esbuild-darwin-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" + integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== + +esbuild-darwin-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" + integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== + +esbuild-freebsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" + integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== + +esbuild-freebsd-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" + integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== + +esbuild-linux-32@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" + integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== + +esbuild-linux-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" + integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== + +esbuild-linux-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" + integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== + +esbuild-linux-arm@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" + integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== + +esbuild-linux-mips64le@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" + integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== + +esbuild-linux-ppc64le@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" + integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== + +esbuild-linux-riscv64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" + integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== + +esbuild-linux-s390x@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" + integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== + +esbuild-netbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" + integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== + +esbuild-openbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" + integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== + +esbuild-sunos-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" + integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== + +esbuild-windows-32@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" + integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== + +esbuild-windows-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" + integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== + +esbuild-windows-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" + integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== + +"esbuild@^0.12 || ^0.13 || ^0.14": + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" + integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== + optionalDependencies: + "@esbuild/linux-loong64" "0.14.54" + esbuild-android-64 "0.14.54" + esbuild-android-arm64 "0.14.54" + esbuild-darwin-64 "0.14.54" + esbuild-darwin-arm64 "0.14.54" + esbuild-freebsd-64 "0.14.54" + esbuild-freebsd-arm64 "0.14.54" + esbuild-linux-32 "0.14.54" + esbuild-linux-64 "0.14.54" + esbuild-linux-arm "0.14.54" + esbuild-linux-arm64 "0.14.54" + esbuild-linux-mips64le "0.14.54" + esbuild-linux-ppc64le "0.14.54" + esbuild-linux-riscv64 "0.14.54" + esbuild-linux-s390x "0.14.54" + esbuild-netbsd-64 "0.14.54" + esbuild-openbsd-64 "0.14.54" + esbuild-sunos-64 "0.14.54" + esbuild-windows-32 "0.14.54" + esbuild-windows-64 "0.14.54" + esbuild-windows-arm64 "0.14.54" escalade@^3.1.1: version "3.1.1" @@ -2964,6 +3098,17 @@ glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globals@^13.6.0, globals@^13.9.0: version "13.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" @@ -4081,6 +4226,13 @@ minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"