diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 3e992f5c8..fa24e3a93 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -19,15 +19,16 @@ jobs: node-version: '18.x' registry-url: 'https://registry.npmjs.org' # Extract the dynamic value from the canary label if present - - name: Extract dynamic value from canary label + - name: Extract CANARY_TAG id: extract-canary run: | - if [[ "${GITHUB_EVENT_LABEL_NAME}" == canary:* ]]; then - CANARY_TAG="${GITHUB_EVENT_LABEL_NAME#canary:}" - echo "CANARY_TAG=${CANARY_TAG}" >> $GITHUB_ENV - fi - env: - GITHUB_EVENT_LABEL_NAME: ${{ github.event.label.name }} + export LABELS_JSON='${{ toJson(github.event.pull_request.labels) }}' + CANARY_TAG=$(node -e " + const labels = JSON.parse(process.env.LABELS_JSON || '[]'); + const canaryLabel = labels.find(label => label.name.startsWith('canary:')); + if (canaryLabel) console.log(canaryLabel.name.split(':')[1]); + ") + echo "CANARY_TAG=$CANARY_TAG" >> $GITHUB_ENV # Ensure that the README is published with the package - run: rm -f packages/cli/README.md && cp README.md packages/cli - run: echo "PR_VERSION=0.0.0-pr.${{github.event.pull_request.number}}.$(git rev-parse --short HEAD)" >> $GITHUB_ENV @@ -36,10 +37,13 @@ jobs: # Publish to npm with the additional tag if CANARY_TAG is set - run: | npm publish --workspace packages/cli --tag experimental - if [[ -n "${{ env.CANARY_TAG }}" ]]; then - npm dist-tag add checkly@${{ env.PR_VERSION }} ${{ env.CANARY_TAG }} - fi + if [[ -n "$CANARY_TAG" ]]; then + echo "Publishing with additional tag: $CANARY_TAG" + npm dist-tag add checkly@$PR_VERSION $CANARY_TAG + fi env: + CANARY_TAG: ${{ env.CANARY_TAG }} + PR_VERSION: ${{ env.PR_VERSION }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - uses: marocchino/sticky-pull-request-comment@v2 with: diff --git a/package-lock.json b/package-lock.json index f4dd6461d..b34981444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4712,6 +4712,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/archiver": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", + "integrity": "sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==", + "license": "MIT", + "dependencies": { + "@types/readdir-glob": "*" + } + }, "node_modules/@types/config": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/config/-/config-3.3.5.tgz", @@ -4833,6 +4842,15 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", @@ -5337,6 +5355,122 @@ "node": ">=14" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/arg": { "version": "4.1.3", "dev": true, @@ -5416,10 +5550,23 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "funding": [ @@ -5495,6 +5642,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "license": "MIT" @@ -5881,6 +6037,62 @@ "node": ">=8" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" @@ -6044,6 +6256,12 @@ "node": ">=14" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -6087,6 +6305,71 @@ "typescript": ">=4" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/create-checkly": { "resolved": "packages/create-cli", "link": true @@ -6862,6 +7145,12 @@ "version": "3.1.3", "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -7287,6 +7576,26 @@ "dev": true, "license": "ISC" }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "dev": true, @@ -7298,6 +7607,39 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -7745,6 +8087,12 @@ "node": ">=8" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "license": "ISC" @@ -7925,6 +8273,48 @@ "node": ">=6" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -9136,6 +9526,15 @@ "node": ">=10" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", @@ -12483,6 +12882,36 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", @@ -12996,6 +13425,19 @@ "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "license": "MIT", @@ -13148,10 +13590,30 @@ "node": ">=10" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "license": "ISC" }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -13986,6 +14448,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "packages/cli": { "name": "checkly", "version": "0.0.1-dev", @@ -13996,9 +14512,11 @@ "@oclif/plugin-not-found": "^3.2.44", "@oclif/plugin-plugins": "^5.4.36", "@oclif/plugin-warn-if-update-available": "^3.1.35", + "@types/archiver": "6.0.3", "@typescript-eslint/typescript-estree": "^8.30.0", "acorn": "^8.14.1", "acorn-walk": "^8.3.4", + "archiver": "7.0.1", "axios": "^1.8.4", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -14013,6 +14531,7 @@ "jwt-decode": "^3.1.2", "log-symbols": "^4.1.0", "luxon": "^3.6.1", + "minimatch": "9.0.5", "mqtt": "^5.11.0", "open": "^8.4.2", "p-queue": "^6.6.2", @@ -14149,26 +14668,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/cli/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "packages/cli/node_modules/human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -14204,15 +14703,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/cli/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "packages/cli/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -14355,16 +14845,6 @@ "undici-types": "~6.21.0" } }, - "packages/create-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "packages/create-cli/node_modules/chalk": { "version": "4.1.2", "license": "MIT", @@ -14379,27 +14859,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "packages/create-cli/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "packages/create-cli/node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -14408,32 +14867,6 @@ "node": ">=8" } }, - "packages/create-cli/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/create-cli/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "packages/create-cli/node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -17656,6 +18089,14 @@ "version": "1.0.3", "dev": true }, + "@types/archiver": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", + "integrity": "sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==", + "requires": { + "@types/readdir-glob": "*" + } + }, "@types/config": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/config/-/config-3.3.5.tgz", @@ -17763,6 +18204,14 @@ } } }, + "@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "requires": { + "@types/node": "*" + } + }, "@types/semver": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", @@ -18041,34 +18490,108 @@ "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } - }, - "json-schema-traverse": { - "version": "1.0.0" + }, + "json-schema-traverse": { + "version": "1.0.0" + } + } + }, + "ansi-escapes": { + "version": "4.3.2", + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==" + }, + "archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "requires": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } + } + }, + "archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "requires": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } } } }, - "ansi-escapes": { - "version": "4.3.2", - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "ansis": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", - "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==" - }, "arg": { "version": "4.1.3", "dev": true @@ -18126,9 +18649,20 @@ "proxy-from-env": "^1.1.0" } }, + "b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" + }, "balanced-match": { "version": "1.0.2" }, + "bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "optional": true + }, "base64-js": { "version": "1.5.1" }, @@ -18168,6 +18702,11 @@ "ieee754": "^1.1.13" } }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==" + }, "buffer-from": { "version": "1.1.2" }, @@ -18305,6 +18844,7 @@ "@oclif/plugin-plugins": "^5.4.36", "@oclif/plugin-warn-if-update-available": "^3.1.35", "@playwright/test": "^1.51.1", + "@types/archiver": "6.0.3", "@types/config": "^3.3.5", "@types/glob": "^8.1.0", "@types/luxon": "^3.6.2", @@ -18317,6 +18857,7 @@ "@typescript-eslint/typescript-estree": "^8.30.0", "acorn": "^8.14.1", "acorn-walk": "^8.3.4", + "archiver": "7.0.1", "axios": "^1.8.4", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -18334,6 +18875,7 @@ "jwt-decode": "^3.1.2", "log-symbols": "^4.1.0", "luxon": "^3.6.1", + "minimatch": "9.0.5", "mqtt": "^5.11.0", "nanoid": "^3.3.11", "oclif": "^4.17.44", @@ -18409,19 +18951,6 @@ "is-stream": "^4.0.1" } }, - "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, "human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -18440,11 +18969,6 @@ "brace-expansion": "^2.0.1" } }, - "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" - }, "npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -18616,6 +19140,41 @@ } } }, + "compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "requires": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } + } + }, "concat-map": { "version": "0.0.1" }, @@ -18734,6 +19293,11 @@ "split2": "^3.2.2" } }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -18753,6 +19317,43 @@ "dev": true, "requires": {} }, + "crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" + }, + "crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } + } + }, "create-checkly": { "version": "file:packages/create-cli", "requires": { @@ -18795,15 +19396,6 @@ "undici-types": "~6.21.0" } }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "chalk": { "version": "4.1.2", "requires": { @@ -18811,40 +19403,11 @@ "supports-color": "^7.1.0" } }, - "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true - }, "ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -19438,6 +20001,11 @@ "fast-deep-equal": { "version": "3.1.3" }, + "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==" + }, "fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -19693,6 +20261,42 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "dev": true }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + } + } + }, "glob-parent": { "version": "6.0.2", "dev": true, @@ -19971,6 +20575,11 @@ "is-docker": "^2.0.0" } }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "isexe": { "version": "2.0.0" }, @@ -20091,6 +20700,43 @@ "kleur": { "version": "3.0.3" }, + "lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -20857,6 +21503,11 @@ "validate-npm-package-license": "^3.0.1" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, "normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", @@ -22995,6 +23646,32 @@ "util-deprecate": "^1.0.1" } }, + "readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "requires": { + "minimatch": "^5.1.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", @@ -23324,6 +24001,16 @@ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true }, + "streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "string_decoder": { "version": "1.3.0", "requires": { @@ -23419,6 +24106,24 @@ } } }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "requires": { + "b4a": "^1.6.4" + } + }, "text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -23878,6 +24583,39 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==" + }, + "zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "requires": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } + } } } } diff --git a/packages/cli/package.json b/packages/cli/package.json index bdce6714e..c98bcae39 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -78,9 +78,11 @@ "@oclif/plugin-not-found": "^3.2.44", "@oclif/plugin-plugins": "^5.4.36", "@oclif/plugin-warn-if-update-available": "^3.1.35", + "@types/archiver": "6.0.3", "@typescript-eslint/typescript-estree": "^8.30.0", "acorn": "^8.14.1", "acorn-walk": "^8.3.4", + "archiver": "7.0.1", "axios": "^1.8.4", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -95,6 +97,7 @@ "jwt-decode": "^3.1.2", "log-symbols": "^4.1.0", "luxon": "^3.6.1", + "minimatch": "9.0.5", "mqtt": "^5.11.0", "open": "^8.4.2", "p-queue": "^6.6.2", diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index ff2d62776..42db23af0 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -11,7 +11,7 @@ import type { Runtime } from '../rest/runtimes' import { Check, AlertChannelSubscription, AlertChannel, CheckGroup, Dashboard, MaintenanceWindow, PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, - Project, ProjectData, BrowserCheck, + Project, ProjectData, BrowserCheck, PlaywrightCheck, } from '../constructs' import chalk from 'chalk' import { splitConfigFilePath, getGitInformation } from '../services/util' @@ -114,6 +114,9 @@ export default class Deploy extends AuthCommand { defaultRuntimeId: account.runtimeId, verifyRuntimeDependencies, checklyConfigConstructs, + playwrightConfigPath: checklyConfig.checks?.playwrightConfigPath, + include: checklyConfig.checks?.include, + playwrightChecks: checklyConfig.checks?.playwrightChecks, }) const repoInfo = getGitInformation(project.repoUrl) @@ -128,6 +131,19 @@ export default class Deploy extends AuthCommand { } check.snapshots = await uploadSnapshots(check.rawSnapshots) } + + for (const check of Object.values(project.data.check)) { + // TODO: Improve bundling and uploading + if (!(check instanceof PlaywrightCheck) || check.codeBundlePath) { + continue + } + const { + relativePlaywrightConfigPath, browsers, key, + } = await PlaywrightCheck.bundleProject(check.playwrightConfigPath, check.include) + check.codeBundlePath = key + check.browsers = browsers + check.playwrightConfigPath = relativePlaywrightConfigPath + } } const projectPayload = project.synthesize(false) diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index d2301823c..d7b1d17c1 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -16,7 +16,7 @@ import { loadChecklyConfig } from '../services/checkly-config-loader' import { filterByFileNamePattern, filterByCheckNamePattern, filterByTags } from '../services/test-filters' import type { Runtime } from '../rest/runtimes' import { AuthCommand } from './authCommand' -import { BrowserCheck, Check, HeartbeatCheck, MultiStepCheck, Project, RetryStrategyBuilder, Session } from '../constructs' +import { BrowserCheck, Check, HeartbeatCheck, MultiStepCheck, PlaywrightCheck, Project, RetryStrategyBuilder, Session } from '../constructs' import type { Region } from '..' import { splitConfigFilePath, getGitInformation, getCiInformation, getEnvs } from '../services/util' import { createReporters, ReporterType } from '../reporters/reporter' @@ -182,6 +182,9 @@ export default class Test extends AuthCommand { defaultRuntimeId: account.runtimeId, verifyRuntimeDependencies, checklyConfigConstructs, + playwrightConfigPath: checklyConfig.checks?.playwrightConfigPath, + include: checklyConfig.checks?.include, + playwrightChecks: checklyConfig.checks?.playwrightChecks, }) const checks = Object.entries(project.data.check) .filter(([, check]) => { @@ -230,6 +233,19 @@ export default class Test extends AuthCommand { check.snapshots = await uploadSnapshots(check.rawSnapshots) } + for (const check of checks) { + // TODO: Improve bundling and uploading + if (!(check instanceof PlaywrightCheck) || check.codeBundlePath) { + continue + } + const { + relativePlaywrightConfigPath, browsers, key, + } = await PlaywrightCheck.bundleProject(check.playwrightConfigPath, check.include) + check.codeBundlePath = key + check.browsers = browsers + check.playwrightConfigPath = relativePlaywrightConfigPath + } + if (this.fancy) { ux.action.stop() } diff --git a/packages/cli/src/constructs/index.ts b/packages/cli/src/constructs/index.ts index fd9c6e4c1..4c4cf0a70 100644 --- a/packages/cli/src/constructs/index.ts +++ b/packages/cli/src/constructs/index.ts @@ -32,3 +32,4 @@ export * from './telegram-alert-channel' export * from './status-page' export * from './status-page-service' export * from './incident' +export * from './playwright-check' diff --git a/packages/cli/src/constructs/playwright-check.ts b/packages/cli/src/constructs/playwright-check.ts new file mode 100644 index 000000000..f20451527 --- /dev/null +++ b/packages/cli/src/constructs/playwright-check.ts @@ -0,0 +1,134 @@ +import fs from 'fs' +import type { AxiosResponse } from 'axios' +import { Check, CheckProps } from './check' +import { Session } from './project' +import { + bundlePlayWrightProject, cleanup, +} from '../services/util' +import { checklyStorage } from '../rest/api' +import { ValidationError } from './validator-error' + +export interface PlaywrightCheckProps extends CheckProps { + playwrightConfigPath: string + codeBundlePath?: string + installCommand?: string + testCommand?: string + pwProjects?: string|string[] + pwTags?: string|string[] + browsers?: string[] + include?: string|string[] + groupName?: string + logicalId: string + cacheHash?: string +} + +export class PlaywrightCheck extends Check { + installCommand?: string + testCommand: string + playwrightConfigPath: string + pwProjects: string[] + pwTags: string[] + codeBundlePath?: string + browsers?: string[] + include: string[] + groupName?: string + name: string + cacheHash?: string + constructor (logicalId: string, props: PlaywrightCheckProps) { + super(logicalId, props) + this.logicalId = logicalId + this.name = props.name + this.cacheHash = props.cacheHash + this.codeBundlePath = props.codeBundlePath + this.installCommand = props.installCommand + this.browsers = props.browsers + this.pwProjects = props.pwProjects + ? (Array.isArray(props.pwProjects) ? props.pwProjects : [props.pwProjects]) + : [] + this.pwTags = props.pwTags + ? (Array.isArray(props.pwTags) ? props.pwTags : [props.pwTags]) + : [] + this.include = props.include + ? (Array.isArray(props.include) ? props.include : [props.include]) + : [] + this.testCommand = props.testCommand ?? 'npx playwright test' + if (!fs.existsSync(props.playwrightConfigPath)) { + throw new ValidationError(`Playwright config doesnt exist ${props.playwrightConfigPath}`) + } + this.groupName = props.groupName + this.playwrightConfigPath = props.playwrightConfigPath + this.applyGroup(this.groupName) + Session.registerConstruct(this) + } + + applyGroup (groupName?: string) { + if (!groupName) { + return + } + const checkGroups = Session.project?.data?.['check-group'] + if (!checkGroups) { + return + } + const group = Object.values(checkGroups).find(group => group.name === groupName) + if (group) { + this.groupId = group.ref() + } else { + throw new ValidationError(`Error: No group named "${groupName}". Please verify the group exists in your code or create it.`) + } + } + + getSourceFile () { + return this.__checkFilePath ?? this.logicalId + } + + static buildTestCommand ( + testCommand: string, playwrightConfigPath: string, playwrightProject?: string[], playwrightTag?: string[], + ) { + const quotedPath = `"${playwrightConfigPath}"` + const projectArg = playwrightProject?.length ? ' --project ' + playwrightProject.map(p => `"${p}"`).join(' ') : '' + const tagArg = playwrightTag?.length ? ' --grep "' + playwrightTag.join('|').replace(/"/g, '\\"') + '"' : '' + return `${testCommand} --config ${quotedPath}${projectArg}${tagArg}` + } + + static async bundleProject (playwrightConfigPath: string, include: string[]) { + let dir = '' + try { + const { + outputFile, browsers, relativePlaywrightConfigPath, cacheHash, + } = await bundlePlayWrightProject(playwrightConfigPath, include) + dir = outputFile + const { data: { key } } = await PlaywrightCheck.uploadPlaywrightProject(dir) + return { key, browsers, relativePlaywrightConfigPath, cacheHash } + } finally { + await cleanup(dir) + } + } + + static async uploadPlaywrightProject (dir: string): Promise { + const { size } = await fs.promises.stat(dir) + const stream = fs.createReadStream(dir) + stream.on('error', (err) => { + throw new Error(`Failed to read Playwright project file: ${err.message}`) + }) + return checklyStorage.uploadCodeBundle(stream, size) + } + + + synthesize () { + const testCommand = PlaywrightCheck.buildTestCommand( + this.testCommand, + this.playwrightConfigPath, + this.pwProjects, + this.pwTags, + ) + return { + ...super.synthesize(), + checkType: 'PLAYWRIGHT', + codeBundlePath: this.codeBundlePath, + testCommand, + installCommand: this.installCommand, + browsers: this.browsers, + cacheHash: this.cacheHash, + } + } +} diff --git a/packages/cli/src/reporters/list.ts b/packages/cli/src/reporters/list.ts index b17e8b877..ea3daa8f6 100644 --- a/packages/cli/src/reporters/list.ts +++ b/packages/cli/src/reporters/list.ts @@ -42,14 +42,14 @@ export default class ListReporter extends AbstractListReporter { printLn(formatCheckTitle(CheckStatus.RETRIED, checkResult, { printRetryDuration: true })) printLn(indentString(formatCheckResult(checkResult), 4), 2, 1) if (links) { - printLn(indentString('View result: ' + chalk.underline.cyan(`${links.testResultLink}`), 4)) + printLn(indentString('View result: ' + chalk.underline.cyan(links.testResultLink), 4)) if (links.testTraceLinks?.length) { // TODO: print all video files URLs - printLn(indentString('View trace : ' + chalk.underline.cyan(links.testTraceLinks.join(', ')), 4)) + printLn(indentString('View trace : ' + links.testTraceLinks.map(link => chalk.underline.cyan(link)).join(', '), 4)) } if (links.videoLinks?.length) { // TODO: print all trace files URLs - printLn(indentString('View video : ' + chalk.underline.cyan(`${links.videoLinks.join(', ')}`), 4)) + printLn(indentString('View video : ' + links.videoLinks.map(link => chalk.underline.cyan(link)).join(', '), 4)) } printLn('') } @@ -76,14 +76,14 @@ export default class ListReporter extends AbstractListReporter { } if (links) { - printLn(indentString('View result: ' + chalk.underline.cyan(`${links.testResultLink}`), 4)) + printLn(indentString('View result: ' + chalk.underline.cyan(links.testResultLink), 4)) if (links.testTraceLinks?.length) { // TODO: print all video files URLs - printLn(indentString('View trace : ' + chalk.underline.cyan(links.testTraceLinks.join(', ')), 4)) + printLn(indentString('View trace : ' + links.testTraceLinks.map(link => chalk.underline.cyan(link)).join(', '), 4)) } if (links.videoLinks?.length) { // TODO: print all trace files URLs - printLn(indentString('View video : ' + chalk.underline.cyan(`${links.videoLinks.join(', ')}`), 4)) + printLn(indentString('View video : ' + links.videoLinks.map(link => chalk.underline.cyan(link)).join(', '), 4)) } printLn('') } diff --git a/packages/cli/src/rest/checkly-storage.ts b/packages/cli/src/rest/checkly-storage.ts index 114ab3d7d..a45d13c08 100644 --- a/packages/cli/src/rest/checkly-storage.ts +++ b/packages/cli/src/rest/checkly-storage.ts @@ -15,6 +15,14 @@ class ChecklyStorage { ) } + uploadCodeBundle (stream: Readable, size: number) { + return this.api.post<{ key: string }>( + '/next/checkly-storage/upload-code-bundle', + stream, + { headers: { 'Content-Type': 'application/octet-stream', 'content-length': size } }, + ) + } + download (key: string) { return this.api.post('/next/checkly-storage/download', { key }, { responseType: 'stream' }) } diff --git a/packages/cli/src/services/__tests__/fixtures/playwright-configs/simple-config-no-browsers.ts b/packages/cli/src/services/__tests__/fixtures/playwright-configs/simple-config-no-browsers.ts new file mode 100644 index 000000000..175bb224b --- /dev/null +++ b/packages/cli/src/services/__tests__/fixtures/playwright-configs/simple-config-no-browsers.ts @@ -0,0 +1,43 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + testMatch: 'tests.*.ts', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'Chromium', + } + ], + +}); diff --git a/packages/cli/src/services/__tests__/fixtures/playwright-configs/simple-config.ts b/packages/cli/src/services/__tests__/fixtures/playwright-configs/simple-config.ts new file mode 100644 index 000000000..719ca802c --- /dev/null +++ b/packages/cli/src/services/__tests__/fixtures/playwright-configs/simple-config.ts @@ -0,0 +1,65 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 7'] }, + snapshotDir: './snapshots-test', + snapshotPathTemplate: '{testDir}/snapshots-template/{arg}{ext}' + }, + + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + + /* Test against branded browsers. */ + { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + }, + ], + +}); diff --git a/packages/cli/src/services/__tests__/playwright-config.spec.ts b/packages/cli/src/services/__tests__/playwright-config.spec.ts new file mode 100644 index 000000000..1cdcbcd33 --- /dev/null +++ b/packages/cli/src/services/__tests__/playwright-config.spec.ts @@ -0,0 +1,22 @@ +import path from "node:path"; +import { PlaywrightConfig } from "../playwright-config"; +import { describe, it, expect } from 'vitest' +import { Session } from '../../constructs' + + +const fixturesPath = path.join(__dirname, 'fixtures', 'playwright-configs') + +describe('playwright-config', () => { + it('it should load simple config correctly', async () => { + const pwConfig = await Session.loadFile(path.join(fixturesPath, 'simple-config.ts')) + const config = new PlaywrightConfig(fixturesPath, pwConfig) + expect(Array.from(config.testMatch)).toEqual(['**/*.@(spec|test).?(c|m)[jt]s?(x)']) + expect(config.getBrowsers()).toEqual(['chromium', 'webkit', 'msedge', 'chrome']) + }) + it('it should load simple config correctly', async () => { + const pwConfig = await Session.loadFile(path.join(fixturesPath,'simple-config-no-browsers.ts')) + const config = new PlaywrightConfig(fixturesPath, pwConfig) + expect(Array.from(config.testMatch)).toEqual(['tests.*.ts']) + expect(config.getBrowsers()).toEqual(['chromium']) + }) +}) diff --git a/packages/cli/src/services/__tests__/project-parser.spec.ts b/packages/cli/src/services/__tests__/project-parser.spec.ts index deb5b742c..4328d2c8a 100644 --- a/packages/cli/src/services/__tests__/project-parser.spec.ts +++ b/packages/cli/src/services/__tests__/project-parser.spec.ts @@ -222,14 +222,13 @@ describe('parseProject()', () => { runtimeId: '2023.09', }, }) - expect(project.synthesize()).toMatchObject({ - project: { - logicalId: 'glob-project-id', - }, - resources: [ - { type: 'check', logicalId: '__checks__/browser/check2.spec.js' }, - { type: 'check', logicalId: '__checks__/multistep/check1.spec.js' }, - ], + const synthesizedProject = project.synthesize() + expect(synthesizedProject.project.logicalId).toEqual('glob-project-id') + expect(synthesizedProject.resources).toHaveLength(2) + const checkLogicalIds = ['__checks__/browser/check2.spec.js', '__checks__/multistep/check1.spec.js'] + synthesizedProject.resources.forEach((resource) => { + expect(resource.type).toEqual('check') + expect(checkLogicalIds).toContain(resource.logicalId) }) }) }) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/playwright.config.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/playwright.config.ts new file mode 100644 index 000000000..f61bdd0b4 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/playwright.config.ts @@ -0,0 +1,59 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 7'] }, + }, + + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run start', + url: 'http://127.0.0.1:3000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/tests/example.spec.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/tests/example.spec.ts new file mode 100644 index 000000000..b7ab5cbaa --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/tests/example.spec.ts @@ -0,0 +1,11 @@ +import {test, expect, chromium} from '@playwright/test'; + +test('Google test', async () => { + const browser = await chromium.launch(); + const context = await browser.newContext(); + const page = await context.newPage(); + + // check start page is displayed + await page.goto('https://google.com'); + await expect(page).toHaveScreenshot() +}); diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/tests/example.spec.ts-snapshots/Google-test-1-Mobile-Chrome-linux.png b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/tests/example.spec.ts-snapshots/Google-test-1-Mobile-Chrome-linux.png new file mode 100644 index 000000000..97ab545d6 Binary files /dev/null and b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project-snapshots/tests/example.spec.ts-snapshots/Google-test-1-Mobile-Chrome-linux.png differ diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project/playwright.config.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project/playwright.config.ts new file mode 100644 index 000000000..74722a9c0 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project/playwright.config.ts @@ -0,0 +1,52 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 7'] }, + }, + + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ], +}); diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project/tests/example.spec.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project/tests/example.spec.ts new file mode 100644 index 000000000..ffefc2e6a --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/playwright-project/tests/example.spec.ts @@ -0,0 +1,10 @@ +import {test, chromium} from '@playwright/test'; + +test('Google test', async () => { + const browser = await chromium.launch(); + const context = await browser.newContext(); + const page = await context.newPage(); + + // check start page is displayed + await page.goto('https://google.com'); +}); diff --git a/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts b/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts index 92e46f644..0ae8e158e 100644 --- a/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts +++ b/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts @@ -3,252 +3,27 @@ import path from 'node:path' import { describe, it, expect } from 'vitest' import { Parser } from '../parser' -import { pathToPosix } from '../../util' +import { PlaywrightConfig } from "../../playwright-config" +import { Session } from '../../../constructs' -describe('project parser - getFilesAndDependencies()', () => { - it('should handle JS file with no dependencies', async () => { - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([path.join(__dirname, 'check-parser-fixtures', 'no-dependencies.js')]) - expect(res.files).toHaveLength(1) - expect(res.errors).toHaveLength(0) - }) - - it('should handle JS file with dependencies', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'simple-example', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.js')]) - const expectedFiles = [ - 'dep1.js', - 'dep2.js', - 'dep3.js', - 'entrypoint.js', - 'module-package/main.js', - 'module-package/package.json', - 'module/index.js', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - expect(res.errors).toHaveLength(0) - }) - - it('Should not repeat files if duplicated', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'simple-example', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.js'), toAbsolutePath('*.js')]) - const expectedFiles = [ - 'dep1.js', - 'dep2.js', - 'dep3.js', - 'entrypoint.js', - 'module-package/main.js', - 'module-package/package.json', - 'module/index.js', - 'unreachable.js', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - expect(res.errors).toHaveLength(0) - }) - - it('should not fail on a non-existing directory', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'simple-example-that-does-not-exist', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('/')]) - expect(res.files).toHaveLength(0) - expect(res.errors).toHaveLength(0) - }) - - it('should parse the cli in less than 400ms', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, '../../../', ...filepath) - const startTimestamp = Date.now().valueOf() - const res = await new Parser({}).getFilesAndDependencies([toAbsolutePath('/index.ts')]) - const endTimestamp = Date.now().valueOf() - expect(res.files).not.toHaveLength(0) - expect(res.errors).toHaveLength(0) - const isCI = process.env.CI === 'true' - expect(endTimestamp - startTimestamp).toBeLessThan(isCI ? 2000 : 400) - }) +const fixturePath = path.join(__dirname, 'check-parser-fixtures') - it('should handle JS file with dependencies glob patterns', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'simple-example', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('*.js'), toAbsolutePath('*.json')]) - const expectedFiles = [ - 'dep1.js', - 'dep2.js', - 'dep3.js', - 'entrypoint.js', - 'module-package/main.js', - 'module-package/package.json', - 'module/index.js', - 'unreachable.js', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - expect(res.errors).toHaveLength(0) - }) - it('should parse typescript dependencies', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'typescript-example', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.ts')]) - const expectedFiles = [ - 'dep1.ts', - 'dep2.ts', - 'dep3.ts', - 'dep4.js', - 'dep5.ts', - 'dep6.ts', - 'entrypoint.ts', - 'module-package/main.js', - 'module-package/package.json', - 'module/index.ts', - 'pages/external.first.page.js', - 'pages/external.second.page.ts', - 'type.ts', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - expect(res.errors).toHaveLength(0) - }) - - it('should parse typescript dependencies using tsconfig', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-sample-project', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('src', 'entrypoint.ts')]) - const expectedFiles = [ - 'lib1/file1.ts', - 'lib1/file2.ts', - 'lib1/folder/file1.ts', - 'lib1/folder/file2.ts', - 'lib1/index.ts', - 'lib1/package.json', - 'lib1/tsconfig.json', - 'lib2/index.ts', - 'lib3/foo/bar.ts', - 'src/entrypoint.ts', - 'tsconfig.json', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - expect(res.errors).toHaveLength(0) - }) - - it('should not include tsconfig if not needed', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-unused', ...filepath) +describe('project parser - getFilesAndDependencies()', () => { + it('should handle spec file', async () => { + const projectPath = path.join(fixturePath, 'playwright-project') + const playwrightConfig = new PlaywrightConfig(projectPath, await Session.loadFile(path.join(projectPath, 'playwright.config.ts'))) const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('src', 'entrypoint.ts')]) - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual([pathToPosix(toAbsolutePath('src', 'entrypoint.ts'))]) + const res = await parser.getFilesAndDependencies(playwrightConfig) + expect(res.files).toHaveLength(1) expect(res.errors).toHaveLength(0) }) - - it('should support importing ts extensions if allowed', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-allow-importing-ts-extensions', ...filepath) + it('should handle a spec file with snapshots', async () => { + const projectPath = path.join(fixturePath, 'playwright-project-snapshots') + const playwrightConfig = new PlaywrightConfig(projectPath, await Session.loadFile(path.join(projectPath, 'playwright.config.ts'))) const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('src', 'entrypoint.ts')]) - const expectedFiles = [ - 'src/dep1.ts', - 'src/dep2.ts', - 'src/dep3.ts', - 'src/entrypoint.ts', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) + const res = await parser.getFilesAndDependencies(playwrightConfig) + expect(res.files).toHaveLength(2) expect(res.errors).toHaveLength(0) }) - - it('should not import TS files from a JS file', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'no-import-ts-from-js', ...filepath) - const parser = new Parser({}) - expect.assertions(1) - try { - await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.js')]) - } catch (err) { - expect(err).toMatchObject({ - missingFiles: [ - pathToPosix(toAbsolutePath('dep1')), - pathToPosix(toAbsolutePath('dep1.ts')), - pathToPosix(toAbsolutePath('dep1.js')), - ], - }) - } - }) - - it('should import JS files from a TS file', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'import-js-from-ts', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.ts')]) - const expectedFiles = [ - 'dep1.js', - 'dep2.js', - 'dep3.ts', - 'entrypoint.ts', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - }) - - it('should handle ES Modules', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'esmodules-example', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.js')]) - const expectedFiles = [ - 'dep1.js', - 'dep2.js', - 'dep3.js', - 'dep5.js', - 'dep6.js', - 'entrypoint.js', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - }) - - it('should handle Common JS and ES Modules', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'common-esm-example', ...filepath) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.mjs')]) - const expectedFiles = [ - 'dep1.js', - 'dep2.mjs', - 'dep3.mjs', - 'dep4.mjs', - 'dep5.mjs', - 'dep6.mjs', - 'entrypoint.mjs', - ].map(file => pathToPosix(toAbsolutePath(file))) - - expect(res.files.map(file => pathToPosix(file)).sort()).toEqual(expectedFiles) - }) - - it('should handle node: prefix for built-ins', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'builtin-with-node-prefix', ...filepath) - const parser = new Parser({}) - await parser.getFilesAndDependencies([toAbsolutePath('entrypoint.ts')]) - }) - - /* - * There is an unhandled edge-case when require() is reassigned. - * Even though the check might execute fine, we throw an error for a missing dependency. - * We could address this by keeping track of assignments as we walk the AST. - */ - it.skip('should ignore cases where require is reassigned', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'reassign-require.js') - const parser = new Parser({}) - await parser.getFilesAndDependencies([entrypoint]) - }) - - // Checks run on Checkly are wrapped to support top level await. - // For consistency with checks created via the UI, the CLI should support this as well. - it('should allow top-level await', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'top-level-await.js') - const parser = new Parser({}) - await parser.getFilesAndDependencies([entrypoint]) - }) - - it('should allow top-level await in TypeScript', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'top-level-await.ts') - const parser = new Parser({}) - await parser.getFilesAndDependencies([entrypoint]) - }) }) diff --git a/packages/cli/src/services/check-parser/parser.ts b/packages/cli/src/services/check-parser/parser.ts index 56f2b0870..482c7e7ac 100644 --- a/packages/cli/src/services/check-parser/parser.ts +++ b/packages/cli/src/services/check-parser/parser.ts @@ -1,17 +1,20 @@ import * as path from 'path' import * as fs from 'fs' +import url from 'url' import * as fsAsync from 'fs/promises' import * as acorn from 'acorn' import * as walk from 'acorn-walk' +import { minimatch } from 'minimatch' import { Collector } from './collector' import { DependencyParseError } from './errors' import { PackageFilesResolver, Dependencies } from './package-files/resolver' // Only import types given this is an optional dependency import type { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree' +import type { PlaywrightConfig } from '../playwright-config' import { findFilesWithPattern, pathToPosix } from '../util' // Our custom configuration to handle walking errors - + const ignore = (_node: any, _st: any, _c: any) => {} type Module = { @@ -21,6 +24,7 @@ type Module = { type SupportedFileExtension = '.js' | '.mjs' | '.ts' const PACKAGE_EXTENSION = `${path.sep}package.json` +const STATIC_FILE_EXTENSION = ['.json', '.txt', '.jpeg', '.jpg', '.png'] const supportedBuiltinModules = [ 'node:assert', @@ -64,7 +68,7 @@ function getTsParser (): any { tsParser = require('@typescript-eslint/typescript-estree') const AST_NODE_TYPES = tsParser.AST_NODE_TYPES as AST_NODE_TYPES // Our custom configuration to handle walking errors - + Object.values(AST_NODE_TYPES).forEach((astType) => { // Only handle the TS specific ones if (!astType.startsWith('TS')) { @@ -129,8 +133,9 @@ export class Parser { } } - async getFilesAndDependencies (paths: string[]): Promise<{ files: string[], errors: string[] }> { - const files = new Set(await this.getFilesFromPaths(paths)) + async getFilesAndDependencies (playwrightConfig: PlaywrightConfig): + Promise<{ files: string[], errors: string[] }> { + const files = new Set(await this.getFilesFromPaths(playwrightConfig)) const errors = new Set() const missingFiles = new Set() const resultFileSet = new Set() @@ -138,7 +143,7 @@ export class Parser { if (resultFileSet.has(file)) { continue } - if (file.endsWith('.json')) { + if (STATIC_FILE_EXTENSION.includes(path.extname(file))) { // Holds info about the main file and doesn't need to be parsed resultFileSet.add(file) continue @@ -174,31 +179,79 @@ export class Parser { resultFileSet.add(pathToPosix(item.filePath)) } if (missingFiles.size) { - throw new DependencyParseError(paths.join(', '), Array.from(missingFiles), [], []) + throw new DependencyParseError([].join(', '), Array.from(missingFiles), [], []) } return { files: Array.from(resultFileSet), errors: Array.from(errors) } } - private async getFilesFromPaths (paths: string[]): Promise { - const files = paths.map(async (currPath) => { - const normalizedPath = pathToPosix(currPath) - try { - const stats = await fsAsync.lstat(normalizedPath) - if (stats.isDirectory()) { - return findFilesWithPattern(normalizedPath, '**/*.{js,ts,mjs}', []) + private async collectFiles(cache: Map, testDir: string, ignoredFiles: string[]) { + let files = cache.get(testDir) + if (!files) { + files = await findFilesWithPattern(testDir, '**/*.{js,ts,mjs}', ignoredFiles) + cache.set(testDir, files) + } + return files + } + + private async getFilesFromPaths (playwrightConfig: (PlaywrightConfig)): Promise { + const ignoredFiles = ['**/node_modules/**', '.git/**'] + const cachedFiles = new Map + // If projects is definited, ignore root settings + const projects = playwrightConfig.projects ?? [playwrightConfig] + for (const project of projects) { + // Cache the files by test dir + const files = await this.collectFiles(cachedFiles, project.testDir, ignoredFiles) + const matcher = this.createFileMatcher(Array.from(project.testMatch)) + for (const file of files) { + if (!matcher(file)) { + continue } - return [normalizedPath] - } catch (err) { - if (normalizedPath.includes('*') || normalizedPath.includes('?') || normalizedPath.includes('{')) { - return findFilesWithPattern(process.cwd(), normalizedPath, []) - } else { - return [] + project.addFiles(file) + const snapshotGlobs = project.getSnapshotPath(file).map(snapshotPath => pathToPosix(snapshotPath)) + const snapshots = await findFilesWithPattern(project.testDir, snapshotGlobs, ignoredFiles) + if (snapshots.length) { + project.addFiles(...snapshots) } } - }) + } + return playwrightConfig.getFiles() + } - const filesArray = await Promise.all(files) - return filesArray.flat() + createFileMatcher(patterns: (string | RegExp)[]): (filePath: string) => boolean { + const reList: RegExp[] = []; + const filePatterns: string[] = []; + for (const pattern of patterns) { + if (pattern instanceof RegExp) { + reList.push(pattern); + } else { + if (!pattern.startsWith('**/')) + filePatterns.push('**/' + pattern); + else + filePatterns.push(pattern); + } + } + return (filePath: string) => { + for (const re of reList) { + re.lastIndex = 0; + if (re.test(filePath)) + return true; + } + // Windows might still receive unix style paths from Cygwin or Git Bash. + // Check against the file url as well. + if (path.sep === '\\') { + const fileURL = url.pathToFileURL(filePath).href; + for (const re of reList) { + re.lastIndex = 0; + if (re.test(fileURL)) + return true; + } + } + for (const pattern of filePatterns) { + if (minimatch(filePath, pattern, { nocase: true, dot: true })) + return true; + } + return false; + } } parse (entrypoint: string) { @@ -215,7 +268,7 @@ export class Parser { while (bfsQueue.length > 0) { // Since we just checked the length, shift() will never return undefined. // We can add a not-null assertion operator (!). - + const item = bfsQueue.shift()! if (item.filePath.endsWith(PACKAGE_EXTENSION)) { diff --git a/packages/cli/src/services/checkly-config-loader.ts b/packages/cli/src/services/checkly-config-loader.ts index 0e6af3b43..256b96704 100644 --- a/packages/cli/src/services/checkly-config-loader.ts +++ b/packages/cli/src/services/checkly-config-loader.ts @@ -1,6 +1,9 @@ import * as path from 'path' import fs from 'node:fs/promises' +import { existsSync } from 'fs' +import { getDefaultChecklyConfig, writeChecklyConfigFile } from './util' import { CheckProps } from '../constructs/check' +import { PlaywrightCheckProps } from '../constructs/playwright-check' import { Session } from '../constructs' import { Construct } from '../constructs/construct' import type { Region } from '..' @@ -12,6 +15,11 @@ export type CheckConfigDefaults = Pick +export type PlaywrightSlimmedProp = Pick & {logicalId: string} + export type ChecklyConfig = { /** * Friendly name for your project. @@ -58,6 +66,19 @@ export type ChecklyConfig = { */ testMatch?: string | string[], }, + /** + * Playwright config path to be used during bundling and playwright config parsing + */ + playwrightConfigPath?: string, + + /** + * Extra files to be included into the playwright bundle + */ + include?: string | string[], + /** + * List of playwright checks that use the defined playwright config path + */ + playwrightChecks?: PlaywrightSlimmedProp[] }, /** * CLI default configuration properties. @@ -125,15 +146,10 @@ export async function loadChecklyConfig (dir: string, filenames = ['checkly.conf } if (!config) { - throw new ConfigNotFoundError(`Unable to locate a config at ${dir} with ${filenames.join(', ')}.`) + config = await handleMissingConfig(dir, filenames) } - for (const field of ['logicalId', 'projectName'] as const) { - const requiredField = config?.[field] - if (!requiredField || !(isString(requiredField))) { - throw new Error(`Config object missing a ${field} as type string`) - } - } + validateConfigFields(config, ['logicalId', 'projectName'] as const) const constructs = Session.checklyConfigFileConstructs Session.loadingChecklyConfigFile = false @@ -143,3 +159,28 @@ export async function loadChecklyConfig (dir: string, filenames = ['checkly.conf } return { config, constructs } } + +async function handleMissingConfig (dir: string, filenames: string[]): Promise { + const baseName = path.basename(dir) + const playwrightConfigPath = findPlaywrightConfigPath(dir) + if (playwrightConfigPath) { + const checklyConfig = getDefaultChecklyConfig(baseName, `./${path.relative(dir, playwrightConfigPath)}`) + await writeChecklyConfigFile(dir, checklyConfig) + return checklyConfig + } + throw new ConfigNotFoundError(`Unable to locate a config at ${dir} with ${filenames.join(', ')}.`) +} + +function findPlaywrightConfigPath (dir: string): string | undefined { + return ['playwright.config.ts', 'playwright.config.js'] + .map(file => path.resolve(dir, file)) + .find(filePath => existsSync(filePath)) +} + +function validateConfigFields (config: ChecklyConfig, fields: (keyof ChecklyConfig)[]): void { + for (const field of fields) { + if (!config?.[field] || !isString(config[field])) { + throw new Error(`Config object missing a ${field} as type string`) + } + } +} diff --git a/packages/cli/src/services/playwright-config.ts b/packages/cli/src/services/playwright-config.ts new file mode 100644 index 000000000..9e9e7e636 --- /dev/null +++ b/packages/cli/src/services/playwright-config.ts @@ -0,0 +1,165 @@ +import * as path from 'node:path' + +function toAbsolutePath(dir: string, file: string) { + return path.resolve(dir, file) +} + +function parseBrowsers(config: any) { + const browsers = new Set() + const browserKeywords = ['browserName', 'defaultBrowserType', 'channel'] + for (const browserKeyword of browserKeywords) { + if (config?.use?.[browserKeyword]) { + browsers.add(config?.use[browserKeyword]) + } + } + return browsers +} + +function buildSnapshotTemplates(config: PlaywrightConfig|PlaywrightProject, filePath: string) { + const fileRelativePath = path.relative(config.testDir, filePath) + const parsed = path.parse(fileRelativePath) + return Array.from(config.snapshotTemplates).map(template => { + return template + .replace(/\{(.)?testDir\}/g, '$1' + config.testDir) + .replace(/\{(.)?snapshotDir\}/g, '$1' + config.snapshotDir) + .replace(/\{(.)?testFileDir\}/g, '$1' + parsed.dir) + .replace(/\{(.)?platform\}/g, '$1' + config.platform) + .replace(/\{(.)?projectName\}/g, config.projectName) + .replace(/\{(.)?testName\}/g, '$1' + '*') + .replace(/\{(.)?testFileName\}/g, '$1' + parsed.base) + .replace(/\{(.)?testFilePath\}/g, '$1' + fileRelativePath) + .replace(/\{(.)?arg\}/g, '$1' + '*') + .replace(/\{(.)?ext\}/g, '$1' + '.*'); + }) +} + +const DEFAULT_SNAPSHOT_TEMPLATE = '{snapshotDir}/{testFilePath}-snapshots/{arg}{ext}' + +export class PlaywrightConfig { + projectName: string + platform: string + testDir: string + snapshotDir: string + testMatch: Set + snapshotTemplates: Set + browsers: Set + projects?: PlaywrightProject[] + files: Set + + constructor(dir: string, playwrightConfig: any) { + this.projectName = '' + this.platform = 'linux' + this.testDir = playwrightConfig.testDir ? toAbsolutePath(dir, playwrightConfig.testDir) : dir + this.snapshotDir = playwrightConfig.snapshotDir ? toAbsolutePath(dir, playwrightConfig.snapshotDir) : this.testDir + this.files = new Set() + this.snapshotTemplates = new Set() + const testMatch = playwrightConfig.testMatch ?? ['**/*.@(spec|test).?(c|m)[jt]s?(x)'] + this.testMatch = new Set(Array.isArray(testMatch) ? testMatch : [testMatch]) + + const fileDefinitions = ['tsconfig', 'globalSetup', 'globalTeardown'] + for (const fileDefinition of fileDefinitions) { + const definition = playwrightConfig[fileDefinition] + if (!definition) { + continue + } + if (Array.isArray(definition)) { + definition.forEach((file: string) => this.files.add(file)) + } else { + this.files.add(definition) + } + } + + if (playwrightConfig.snapshotPathTemplate) { + this.snapshotTemplates.add(playwrightConfig.snapshotPathTemplate) + } + + const expect = playwrightConfig.expect + if (expect?.toHaveScreenshot?.pathTemplate) { + this.snapshotTemplates.add(expect.toHaveScreenshot.pathTemplate) + } + if (expect?.toMatchAriaSnapshot?.pathTemplate) { + this.snapshotTemplates.add(expect.toMatchAriaSnapshot.pathTemplate) + } + if (this.snapshotTemplates.size === 0) { + this.snapshotTemplates.add(DEFAULT_SNAPSHOT_TEMPLATE); + } + + this.browsers = parseBrowsers(playwrightConfig) + + if (playwrightConfig.projects?.length) { + this.projects = playwrightConfig.projects.map((project: any) => new PlaywrightProject(dir, this, project)) + } + } + + getFiles() { + const files = new Set(this.files) + this.projects?.forEach(project => project.files.forEach(file => files.add(file))) + return Array.from(files) + } + + getBrowsers() { + const browsers = new Set(this.browsers) + this.projects?.forEach(project => project.browsers.forEach(browser => browsers.add(browser))) + if (browsers.size === 0) { + // Add the default browser + browsers.add('chromium') + } + return Array.from(browsers) + } + + addFiles(...files: string[]) { + files.forEach(this.files.add, this.files) + } + + getSnapshotPath(filePath: string) { + return buildSnapshotTemplates(this, filePath) + } +} + +export class PlaywrightProject { + projectName: string + platform: string + testDir: string + snapshotDir: string + testMatch: Set + expect: any + files: Set + snapshotTemplates: Set + browsers: Set + constructor(dir: string, playwrightConfig: any, playwrightProject: any) { + this.projectName = playwrightProject.name + this.platform = 'linux' + this.testDir = playwrightProject.testDir ? toAbsolutePath(dir, playwrightProject.testDir): playwrightConfig.testDir + this.snapshotDir = playwrightProject.snapshotDir ? toAbsolutePath(dir, playwrightProject.snapshotDir): (playwrightConfig.snapshotDir ?? this.testDir) + this.files = new Set() + this.snapshotTemplates = new Set() + const testMatch = playwrightProject.testMatch ?? Array.from(playwrightConfig.testMatch) + this.testMatch = new Set(Array.isArray(testMatch) ? testMatch : [testMatch]) + + if (playwrightProject.snapshotPathTemplate) { + this.snapshotTemplates.add(playwrightProject.snapshotPathTemplate) + } + + // Check if the project overrides the global expect field + const expect = playwrightProject.expect ?? playwrightConfig.expect + if (expect?.toHaveScreenshot?.pathTemplate) { + this.snapshotTemplates.add(expect.toHaveScreenshot.pathTemplate) + } + if (expect?.toMatchAriaSnapshot?.pathTemplate) { + this.snapshotTemplates.add(expect.toMatchAriaSnapshot.pathTemplate) + } + if (this.snapshotTemplates.size === 0) { + this.snapshotTemplates.add(DEFAULT_SNAPSHOT_TEMPLATE); + } + + this.browsers = parseBrowsers(playwrightProject) + } + + getSnapshotPath(filePath: string) { + return buildSnapshotTemplates(this, filePath) + } + + addFiles(...files: string[]){ + files.forEach(this.files.add, this.files) + } +} diff --git a/packages/cli/src/services/project-parser.ts b/packages/cli/src/services/project-parser.ts index 1e9d85af1..e4e65ee10 100644 --- a/packages/cli/src/services/project-parser.ts +++ b/packages/cli/src/services/project-parser.ts @@ -1,14 +1,18 @@ import * as path from 'path' -import { findFilesWithPattern, pathToPosix } from './util' +import { + findFilesWithPattern, + + pathToPosix, +} from './util' import { Check, BrowserCheck, CheckGroup, Project, Session, PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, MultiStepCheck, } from '../constructs' import { Ref } from '../constructs/ref' -import { CheckConfigDefaults } from './checkly-config-loader' - +import { CheckConfigDefaults, PlaywrightSlimmedProp } from './checkly-config-loader' import type { Runtime } from '../rest/runtimes' import type { Construct } from '../constructs/construct' +import { PlaywrightCheck } from '../constructs/playwright-check' type ProjectParseOpts = { directory: string, @@ -25,6 +29,9 @@ type ProjectParseOpts = { defaultRuntimeId: string, verifyRuntimeDependencies?: boolean, checklyConfigConstructs?: Construct[], + playwrightConfigPath?: string + include?: string | string[], + playwrightChecks?: PlaywrightSlimmedProp[] } const BASE_CHECK_DEFAULTS = { @@ -46,6 +53,9 @@ export async function parseProject (opts: ProjectParseOpts): Promise { defaultRuntimeId, verifyRuntimeDependencies, checklyConfigConstructs, + playwrightConfigPath, + include, + playwrightChecks, } = opts const project = new Project(projectLogicalId, { name: projectName, @@ -62,11 +72,19 @@ export async function parseProject (opts: ProjectParseOpts): Promise { Session.defaultRuntimeId = defaultRuntimeId Session.verifyRuntimeDependencies = verifyRuntimeDependencies ?? true + const includeWrapped = include + ? (Array.isArray(include) ? include : [include]) + : [] // TODO: Do we really need all of the ** globs, or could we just put node_modules? const ignoreDirectories = ['**/node_modules/**', '**/.git/**', ...ignoreDirectoriesMatch] + await loadAllCheckFiles(directory, checkMatch, ignoreDirectories) - await loadAllBrowserChecks(directory, browserCheckMatch, ignoreDirectories, project) - await loadAllMultiStepChecks(directory, multiStepCheckMatch, ignoreDirectories, project) + await Promise.all([ + loadAllBrowserChecks(directory, browserCheckMatch, ignoreDirectories, project), + loadAllMultiStepChecks(directory, multiStepCheckMatch, ignoreDirectories, project), + loadPlaywrightChecks(playwrightChecks, playwrightConfigPath, includeWrapped), + ]) + // private-location must be processed after all checks and groups are loaded. await loadAllPrivateLocationsSlugNames(project) @@ -74,6 +92,41 @@ export async function parseProject (opts: ProjectParseOpts): Promise { return project } +async function loadPlaywrightChecks ( + playwrightChecks?: PlaywrightSlimmedProp[], playwrightConfigPath?: string, include?: string[]) { + if (!playwrightConfigPath) { + return + } + + if (playwrightChecks?.length) { + for (const playwrightCheckProps of playwrightChecks) { + // TODO: Don't upload for each check + const { + key, browsers, relativePlaywrightConfigPath, cacheHash, + } = await PlaywrightCheck.bundleProject(playwrightConfigPath, include ?? []) + const playwrightCheck = new PlaywrightCheck(playwrightCheckProps.logicalId, { + ...playwrightCheckProps, + codeBundlePath: key, + browsers, + playwrightConfigPath: relativePlaywrightConfigPath, + cacheHash, + }) + } + } else { + const { + key, browsers, relativePlaywrightConfigPath, cacheHash, + } = await PlaywrightCheck.bundleProject(playwrightConfigPath, include ?? []) + const playwrightCheck = new PlaywrightCheck(path.basename(playwrightConfigPath), { + name: path.basename(playwrightConfigPath), + logicalId: path.basename(playwrightConfigPath), + codeBundlePath: key, + browsers, + playwrightConfigPath: relativePlaywrightConfigPath, + cacheHash, + }) + } +} + async function loadAllCheckFiles ( directory: string, checkFilePattern: string | string[], diff --git a/packages/cli/src/services/util.ts b/packages/cli/src/services/util.ts index b9b6196a3..63b1a3b3d 100644 --- a/packages/cli/src/services/util.ts +++ b/packages/cli/src/services/util.ts @@ -7,7 +7,17 @@ import { parse } from 'dotenv' // @ts-ignore import { getProxyForUrl } from 'proxy-from-env' import { httpOverHttp, httpsOverHttp, httpOverHttps, httpsOverHttps } from 'tunnel' +import archiver from 'archiver' +import type { Archiver } from 'archiver' import { glob } from 'glob' +import os from 'node:os' +import { ChecklyConfig } from './checkly-config-loader' +import { Parser } from './check-parser/parser' +import * as JSON5 from 'json5' +import { PlaywrightConfig } from './playwright-config' +import { access , readFile} from 'fs/promises' +import { createHash } from 'crypto'; +import { Session } from '../constructs' export interface GitInformation { commitId: string @@ -163,10 +173,107 @@ export function assignProxy (baseURL: string, axiosConfig: CreateAxiosDefaults) return axiosConfig } +export async function bundlePlayWrightProject (playwrightConfig: string, include: string[]): +Promise<{outputFile: string, browsers: string[], relativePlaywrightConfigPath: string, cacheHash: string}> { + const dir = path.resolve(path.dirname(playwrightConfig)) + const filePath = path.resolve(dir, playwrightConfig) + const pwtConfig = await Session.loadFile(filePath) + const outputFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'cli-')) + const outputFile = path.join(outputFolder, 'playwright-project.tar.gz') + const output = fsSync.createWriteStream(outputFile) + + const archive = archiver('tar', { + gzip: true, + gzipOptions: { + level: 9, + }, + }) + archive.pipe(output) + + const pwConfigParsed = new PlaywrightConfig(dir, pwtConfig) + + const [cacheHash] = await Promise.all([ + getCacheHash(dir), + loadPlaywrightProjectFiles(dir, pwConfigParsed, include, archive) + ]) + + archive.file(playwrightConfig, { name: path.basename(playwrightConfig) }) + await archive.finalize() + return new Promise((resolve, reject) => { + output.on('close', () => { + return resolve({ + outputFile, + browsers: pwConfigParsed.getBrowsers(), + relativePlaywrightConfigPath: path.relative(dir, filePath), + cacheHash + }) + }) + + output.on('error', (err) => { + return reject(err) + }) + }) +} + +export async function getCacheHash (dir: string): Promise { + const lockFile = await findLockFile(dir) + if (!lockFile) { + throw new Error('No lock file found') + } + const fileBuffer = await readFile(lockFile); + const hash = createHash('sha256'); + hash.update(fileBuffer); + return hash.digest('hex'); + +} + +async function findLockFile(dir: string): Promise { + const lockFiles = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]; + + for (const lockFile of lockFiles) { + const filePath = path.join(dir, lockFile); + try { + await access(filePath); + return filePath; + } catch { + // Ignore errors, just check the next file + } + } + return null; // Return null if no lock file is found +} + +export async function loadPlaywrightProjectFiles ( + dir: string, pwConfigParsed: PlaywrightConfig, include: string[], archive: Archiver, +) { + const ignoredFiles = ['**/node_modules/**', '.git/**'] + const parser = new Parser({}) + const { files, errors } = await parser.getFilesAndDependencies(pwConfigParsed) + if (errors.length) { + throw new Error(`Error loading playwright project files: ${errors.map((e: string) => e).join(', ')}`) + } + for (const file of files) { + const relativePath = path.relative(dir, file) + archive.file(file, { name: relativePath }) + } + // TODO: This code below should be a single glob + archive.glob('**/package*.json', { cwd: path.join(dir, '/'), ignore: ignoredFiles }) + archive.glob('**/pnpm*.yaml', { cwd: path.join(dir, '/'), ignore: ignoredFiles }) + archive.glob('**/yarn.lock', { cwd: path.join(dir, '/'), ignore: ignoredFiles }) + for (const includePattern of include) { + archive.glob(includePattern, { cwd: path.join(dir, '/'), ignore: ignoredFiles }) + } +} + +export async function findRegexFiles (directory: string, regex: RegExp, ignorePattern: string[]): + Promise { + const files = await findFilesWithPattern(directory, '**/*.{js,ts,mjs}', ignorePattern) + return files.filter(file => regex.test(file)).map(file => pathToPosix(path.relative(directory, file))) +} + export async function findFilesWithPattern ( directory: string, pattern: string | string[], - ignorePattern: string[], + ignorePattern: string[] ): Promise { // The files are sorted to make sure that the processing order is deterministic. const files = await glob(pattern, { @@ -177,3 +284,41 @@ export async function findFilesWithPattern ( }) return files.sort() } + +export function cleanup (dir: string) { + if (!dir.length) { + return + } + return fs.rm(dir, { recursive: true, force: true }) +} + +export function getDefaultChecklyConfig (directoryName: string, playwrightConfigPath: string): ChecklyConfig { + return { + logicalId: directoryName, + projectName: directoryName, + checks: { + playwrightConfigPath, + playwrightChecks: [ + { + logicalId: directoryName, + name: directoryName, + frequency: 10, + locations: ['us-east-1'], + }, + ], + frequency: 10, + locations: ['us-east-1'], + }, + cli: { + runLocation: 'us-east-1', + }, + } +} + +export async function writeChecklyConfigFile (dir: string, config: ChecklyConfig) { + const configFile = path.join(dir, 'checkly.config.ts') + const configContent = + `import { defineConfig } from 'checkly'\n\nconst config = defineConfig(${JSON5.stringify(config, null, 2)})\n\nexport default config` + + await fs.writeFile(configFile, configContent, { encoding: 'utf-8' }) +}