diff --git a/.eslintrc.json b/.eslintrc.json index b88df90..72e3060 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,10 @@ { - "env": { - "es2022": true - }, - "root": true, - "plugins": ["@typescript-eslint", "prettier"], "overrides": [ { "files": ["*.ts"], + "plugins": ["@typescript-eslint", "prettier"], "parser": "@typescript-eslint/parser", + "env": { "es2022": true }, "parserOptions": { "sourceType": "module", "project": "./tsconfig.json" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7450d1d..0000000 --- a/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright © 2023 flakey5 - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5ec1e36 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 flakey5 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index f55635a..1387c28 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -# dist-worker +# release-cloudflare-worker + New infra for serving Node.js downloads and documentation. For more information, check out [nodejs/build#3461](https://github.com/nodejs/build/issues/3461). ## Dev Setup + View [docs/dev-setup.md](./docs/dev-setup.md) ## License + This repo is licensed under the terms of the [MIT License](./LICENSE.md). It is based off of [Kotx's render worker](https://github.com/kotx/render), which is also licensed under the MIT license. diff --git a/docs/deploy.md b/docs/deploy.md index 0d75fcd..22f18aa 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,15 +1,19 @@ # Deploy + This worker is auto-deployed by Github Actions. The workflow is defined [here](../.github/workflows/deploy.yml). ## Staging + This worker is deployed to staging every time a pr is merged into the main branch. ## Prod + This worker is deployed into prod by a manual trigger. ## Actions Setup + How to setup the actions for automated deployments. - * Create a Cloudflare API Token (https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) - * Set Github secret `CF_API_TOKEN` on the repo to the token you generated - * They should be working now +- Create a Cloudflare API Token (https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) +- Set Github secret `CF_API_TOKEN` on the repo to the token you generated +- They should be working now diff --git a/docs/dev-setup.md b/docs/dev-setup.md index 476a46e..8acc411 100644 --- a/docs/dev-setup.md +++ b/docs/dev-setup.md @@ -1,28 +1,35 @@ # Dev Setup + Guide to setting up this worker for development. ## Have Node Installed + Node needs to be installed for the thing that serves Node downloads (latest LTS/even numbered major recommended) ## Install Dependencies + Run `npm install` ## Testing + To run unit tests, `npm run test:unit`. To run e2e (end-to-end) tests, `npm run test:e2e`. See the [/test](../tests/) folder for more info on testing. ## Running Locally + Spin up a Workerd instance on your machine that serves this worker ### Login to Cloudflare Dash From Wrangler CLI + Run `wrangler login` ### R2 Buckets + Depending on the environment you're running in, you will need a different R2 bucket on your account. For dev (the default), it's `dist-dev`. For staging, `dist-staging`. For prod, `dist-prod`. These buckets will either need to have a copy of Node's dist folder in them or something mimicing the folder there. ### Starting the Local Server -Run `npm start`. This starts a Workerd instance in remote mode. +Run `npm start`. This starts a Workerd instance in remote mode. diff --git a/package-lock.json b/package-lock.json index a33a18a..a97a647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,28 @@ { - "name": "node-with-r2-poc", + "name": "release-cloudflare-worker", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "node-with-r2-poc", + "name": "release-cloudflare-worker", "version": "0.0.0", "dependencies": { - "zod": "^3.22.2" + "handlebars": "^4.7.8" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20230419.0", - "@types/node": "^20.5.8", - "@typescript-eslint/eslint-plugin": "^6.5.0", - "@typescript-eslint/parser": "^6.5.0", - "eslint": "^8.48.0", + "@cloudflare/workers-types": "^4.20230922.0", + "@types/node": "^20.7.0", + "@typescript-eslint/eslint-plugin": "^6.7.3", + "@typescript-eslint/parser": "^6.7.3", + "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", - "prettier": "^3.0.1", - "tsx": "^3.12.8", - "typescript": "^5.0.4", - "wrangler": "^3.0.0" + "prettier": "^3.0.3", + "tsx": "^3.13.0", + "typescript": "^5.2.2", + "wrangler": "^3.10.0", + "zod": "^3.22.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -43,9 +44,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230904.0.tgz", - "integrity": "sha512-/GDlmxAFbDtrQwP4zOXFbqOfaPvkDxdsCoEa+KEBcAl5uR98+7WW5/b8naBHX+t26uS7p4bLlImM8J5F1ienRQ==", + "version": "1.20230922.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230922.0.tgz", + "integrity": "sha512-g1hkVhLna0ICfg1l4iYOTAlfvqzZ4RD/wu5yYFaEOVwA9HlKcB9axmQxCSmeHTHfC763RqXdfBFVgBabp0SK+A==", "cpu": [ "x64" ], @@ -59,9 +60,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230904.0.tgz", - "integrity": "sha512-x8WXNc2xnDqr5y1iirnNdyx8GZY3rL5xiF7ebK3mKQeB+jFjkhO71yuPTkDCzUWtOvw1Wfd4jbwy4wxacMX4mQ==", + "version": "1.20230922.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230922.0.tgz", + "integrity": "sha512-FMPN7VO6tG3DWUw4XLTB3bL/UKIo0P2aghXC6BG6QxdzLqPMxXWRRfLahdFYc3uPz0ehqrZaQR5Wybck7b9Bdg==", "cpu": [ "arm64" ], @@ -75,9 +76,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230904.0.tgz", - "integrity": "sha512-V58xyMS3oDpKO8Dpdh0r0BXm99OzoGgvWe9ufttVraj/1NTMGELwb6i9ySb8k3F1J9m/sO26+TV7pQc/bGC1VQ==", + "version": "1.20230922.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230922.0.tgz", + "integrity": "sha512-EDRdGVgOdd14jt2LJHne3CueUvjnH6lnpAtETj0Ce0SkbdW27GY/YARtcGcPBGO1AKrEnXvMdnvV6EVYp1Yl/w==", "cpu": [ "x64" ], @@ -91,9 +92,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230904.0.tgz", - "integrity": "sha512-VrDaW+pjb5IAKEnNWtEaFiG377kXKmk5Fu0Era4W+jKzPON2BW/qRb/4LNHXQ4yxg/2HLm7RiUTn7JZtt1qO6A==", + "version": "1.20230922.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230922.0.tgz", + "integrity": "sha512-QDf3JqRDwnxdCFni/bLWElJowf5xNmk1h2n4nBB30k1lvcfFiQ0HXgbBMhs2W/x/VUUT2j+hAoIGmvkSNlIj4w==", "cpu": [ "arm64" ], @@ -107,9 +108,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230904.0.tgz", - "integrity": "sha512-/R/dE8uy+8J2YeXfDhI8/Bg7YUirdbbjH5/l/Vv00ZRE0lC3nPLcYeyBXSwXIQ6/Xht3gN+lksLQgKd0ZWRd+Q==", + "version": "1.20230922.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230922.0.tgz", + "integrity": "sha512-Tzoq64YMcHjXRheGyWKHNAfklHS9Us2M1lNZ/6y6ziNB0tF06RNRuD5yRhH1LulSOMxVH/KQAqZ0pNEpt3XyPQ==", "cpu": [ "x64" ], @@ -123,41 +124,11 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20230914.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230914.0.tgz", - "integrity": "sha512-OVeN4lFVu1O0PJGZ2d0FwpK8lelFcr33qYOgCh77ErEYmEBO4adwnIxcIsdQbFbhF0ffN6joiVcljD4zakdaeQ==", + "version": "4.20230922.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230922.0.tgz", + "integrity": "sha512-0N3fg4uv9pS7PXMVYmEmEGs1P1yzMIsqym0sO//o2IjtZhsf7RwOozJtdazDAL0fBIWsl1PRX7BmKe92RV+5qw==", "dev": true }, - "node_modules/@esbuild-kit/cjs-loader": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.4.tgz", - "integrity": "sha512-NfsJX4PdzhwSkfJukczyUiZGc7zNNWZcEAyqeISpDnn0PTfzMJR1aR8xAIPskBejIxBJbIgCCMzbaYa9SXepIg==", - "dev": true, - "dependencies": { - "@esbuild-kit/core-utils": "^3.2.3", - "get-tsconfig": "^4.7.0" - } - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", - "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", - "dev": true, - "dependencies": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", - "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", - "dev": true, - "dependencies": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" - } - }, "node_modules/@esbuild-plugins/node-globals-polyfill": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", @@ -580,9 +551,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -683,28 +654,28 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.6.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.3.tgz", - "integrity": "sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==", + "version": "20.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz", + "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==", "dev": true }, "node_modules/@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.2.tgz", - "integrity": "sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz", + "integrity": "sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.2", - "@typescript-eslint/type-utils": "6.7.2", - "@typescript-eslint/utils": "6.7.2", - "@typescript-eslint/visitor-keys": "6.7.2", + "@typescript-eslint/scope-manager": "6.7.3", + "@typescript-eslint/type-utils": "6.7.3", + "@typescript-eslint/utils": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -730,15 +701,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.2.tgz", - "integrity": "sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz", + "integrity": "sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.2", - "@typescript-eslint/types": "6.7.2", - "@typescript-eslint/typescript-estree": "6.7.2", - "@typescript-eslint/visitor-keys": "6.7.2", + "@typescript-eslint/scope-manager": "6.7.3", + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/typescript-estree": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3", "debug": "^4.3.4" }, "engines": { @@ -758,13 +729,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz", - "integrity": "sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.3.tgz", + "integrity": "sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.2", - "@typescript-eslint/visitor-keys": "6.7.2" + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -775,13 +746,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz", - "integrity": "sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.3.tgz", + "integrity": "sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.2", - "@typescript-eslint/utils": "6.7.2", + "@typescript-eslint/typescript-estree": "6.7.3", + "@typescript-eslint/utils": "6.7.3", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -802,9 +773,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.2.tgz", - "integrity": "sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.3.tgz", + "integrity": "sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -815,13 +786,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz", - "integrity": "sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.3.tgz", + "integrity": "sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.2", - "@typescript-eslint/visitor-keys": "6.7.2", + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/visitor-keys": "6.7.3", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -842,17 +813,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.2.tgz", - "integrity": "sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.3.tgz", + "integrity": "sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.2", - "@typescript-eslint/types": "6.7.2", - "@typescript-eslint/typescript-estree": "6.7.2", + "@typescript-eslint/scope-manager": "6.7.3", + "@typescript-eslint/types": "6.7.3", + "@typescript-eslint/typescript-estree": "6.7.3", "semver": "^7.5.4" }, "engines": { @@ -867,12 +838,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz", - "integrity": "sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.3.tgz", + "integrity": "sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.2", + "@typescript-eslint/types": "6.7.3", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1357,15 +1328,15 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", + "@eslint/js": "8.50.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1743,9 +1714,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.1.tgz", - "integrity": "sha512-sLtd6Bcwbi9IrAow/raCOTE9pmhvo5ksQo5v2lApUGJMzja64MUYhBp0G6X1S+f7IrBPn1HP+XkS2w2meoGcjg==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -1833,6 +1804,26 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2177,9 +2168,9 @@ } }, "node_modules/miniflare": { - "version": "3.20230918.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230918.0.tgz", - "integrity": "sha512-Dd29HB7ZlT1CXB2tPH8nW6fBOOXi/m7qFZHjKm2jGS+1OaGfrv0PkT5UspWW5jQi8rWI87xtordAUiIJkwWqRw==", + "version": "3.20230922.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230922.0.tgz", + "integrity": "sha512-1h8c7b0Ouoml7TmU2mtJo4k/DKMX4ift1vOxyfcktPY/0lqeiRNYulcOCPcF94maI4uATdBIO6Fx/zN2c2Ew0A==", "dev": true, "dependencies": { "acorn": "^8.8.0", @@ -2190,7 +2181,7 @@ "source-map-support": "0.5.21", "stoppable": "^1.1.0", "undici": "^5.22.1", - "workerd": "1.20230904.0", + "workerd": "1.20230922.0", "ws": "^8.11.0", "youch": "^3.2.2", "zod": "^3.20.6" @@ -2211,6 +2202,14 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2250,6 +2249,11 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -2806,7 +2810,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2970,20 +2973,20 @@ "dev": true }, "node_modules/tsx": { - "version": "3.12.10", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.10.tgz", - "integrity": "sha512-2+46h4xvUt1aLDNvk5YBT8Uzw+b7BolGbn7iSMucYqCXZiDc+1IMghLVdw8kKjING32JFOeO+Am9posvjkeclA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.13.0.tgz", + "integrity": "sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==", "dev": true, "dependencies": { - "@esbuild-kit/cjs-loader": "^2.4.2", - "@esbuild-kit/core-utils": "^3.3.0", - "@esbuild-kit/esm-loader": "^2.6.3" + "esbuild": "~0.18.20", + "get-tsconfig": "^4.7.2", + "source-map-support": "^0.5.21" }, "bin": { - "tsx": "dist/cli.js" + "tsx": "dist/cli.mjs" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" } }, "node_modules/type-check": { @@ -3023,10 +3026,22 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici": { - "version": "5.25.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.1.tgz", - "integrity": "sha512-nTw6b2G2OqP6btYPyghCgV4hSwjJlL/78FMJatVLCa3otj6PCOQSt6dVtYt82OtNqFz8XsnJ+vsXLADPXjPhqw==", + "version": "5.25.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.2.tgz", + "integrity": "sha512-tch8RbCfn1UUH1PeVCXva4V8gDpGAud/w0WubD6sHC46vYQ3KDxL+xv1A2UxK0N6jrVedutuPHxe1XIoqerwMw==", "dev": true, "dependencies": { "busboy": "^1.6.0" @@ -3068,10 +3083,15 @@ "node": ">= 8" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/workerd": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230904.0.tgz", - "integrity": "sha512-t9znszH0rQGK4mJGvF9L3nN0qKEaObAGx0JkywFtAwH8OkSn+YfQbHNZE+YsJ4qa1hOz1DCNEk08UDFRBaYq4g==", + "version": "1.20230922.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230922.0.tgz", + "integrity": "sha512-iFe0tqoHsxgZXCaURVNKYtc0QnWdjORZJ0WzaasdSK4STOwV1aD7yC2v5o3HwnNTaJOpNt7H09AYWKB3y1ju8A==", "dev": true, "hasInstallScript": true, "bin": { @@ -3081,17 +3101,17 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20230904.0", - "@cloudflare/workerd-darwin-arm64": "1.20230904.0", - "@cloudflare/workerd-linux-64": "1.20230904.0", - "@cloudflare/workerd-linux-arm64": "1.20230904.0", - "@cloudflare/workerd-windows-64": "1.20230904.0" + "@cloudflare/workerd-darwin-64": "1.20230922.0", + "@cloudflare/workerd-darwin-arm64": "1.20230922.0", + "@cloudflare/workerd-linux-64": "1.20230922.0", + "@cloudflare/workerd-linux-arm64": "1.20230922.0", + "@cloudflare/workerd-windows-64": "1.20230922.0" } }, "node_modules/wrangler": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.9.0.tgz", - "integrity": "sha512-Ho1A76KxbqfcRgCsuN6xGar3BVPyn4oVWM9zx0HvEVhT9wQ7n/LvB6GlPdXKABqEBYhVe/oTH72S5TgWl0DgaA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.10.0.tgz", + "integrity": "sha512-426QvIxEU7JDiZrsCF+B84lWw9BeMVcH4kzn9MZlVTqUD21CbLS1bEs9pdaB8nsnB9Y/n5JTH8bv/vpTBO0Z4g==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", @@ -3100,7 +3120,7 @@ "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.17.19", - "miniflare": "3.20230918.0", + "miniflare": "3.20230922.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "selfsigned": "^2.0.1", @@ -3560,9 +3580,9 @@ } }, "node_modules/youch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.1.tgz", - "integrity": "sha512-Rg9ioi+AkKyje2Hk4qILSVvayaFW98KTsOJ4aIkjDf97LZX5WJVIHZmFLnM4ThcVofHo/fbbwtYajfBPHFOVtg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.2.tgz", + "integrity": "sha512-9cwz/z7abtcHOIuH45nzmUFCZbyJA1nLqlirKvyNRx4wDMhqsBaifAJzBej7L4fsVPjFxYq3NK3GAcfvZsydFw==", "dev": true, "dependencies": { "cookie": "^0.5.0", @@ -3574,6 +3594,7 @@ "version": "3.22.2", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", + "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index e50ded4..5a297f6 100644 --- a/package.json +++ b/package.json @@ -5,26 +5,27 @@ "type": "module", "scripts": { "start": "wrangler dev --remote", - "format": "prettier -u --write \"**/*.{ts,js,json}\"", - "check-format": "prettier -u --check \"**/*.{ts,js,json}\"", + "format": "prettier --check --write \"**/*.{ts,js,json,md,hbs}\"", + "prettier": "prettier --check \"**/*.{ts,js,json,md,hbs}\"", "lint": "eslint ./src", "test:unit": "node --loader tsx ./tests/unit/util.test.ts", "test:e2e": "wrangler deploy --dry-run --outdir=dist && node --loader tsx ./tests/e2e/index.test.ts" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20230419.0", - "@types/node": "^20.5.8", - "@typescript-eslint/eslint-plugin": "^6.5.0", - "@typescript-eslint/parser": "^6.5.0", - "eslint": "^8.48.0", + "@cloudflare/workers-types": "^4.20230922.0", + "@types/node": "^20.7.0", + "@typescript-eslint/eslint-plugin": "^6.7.3", + "@typescript-eslint/parser": "^6.7.3", + "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", - "prettier": "^3.0.1", - "tsx": "^3.12.8", - "typescript": "^5.0.4", - "wrangler": "^3.0.0" + "prettier": "^3.0.3", + "tsx": "^3.13.0", + "typescript": "^5.2.2", + "wrangler": "^3.10.0", + "zod": "^3.22.2" }, "dependencies": { - "zod": "^3.22.2" + "handlebars": "^4.7.8" } } diff --git a/scripts/origin/README.md b/scripts/origin/README.md index 7990cde..5e3f94b 100644 --- a/scripts/origin/README.md +++ b/scripts/origin/README.md @@ -1,2 +1,3 @@ # `scripts/origin/` + Scripts that are stored & ran on the origin server diff --git a/src/handlers/get.ts b/src/handlers/get.ts index 3427881..73e8b9c 100644 --- a/src/handlers/get.ts +++ b/src/handlers/get.ts @@ -6,11 +6,12 @@ import { parseUrl, } from '../util'; import { Handler } from './handler'; -import { listDirectory } from './helpers/directory'; -import { getFile } from './helpers/file'; +import { listDirectory } from './helpers/directoryListing'; +import { getFile } from './helpers/serveFile'; const getHandler: Handler = async (request, env, ctx, cache) => { const shouldServeCache = isCacheEnabled(env); + if (shouldServeCache) { // Caching is enabled, let's see if the request is cached const response = await cache.match(request); @@ -21,6 +22,7 @@ const getHandler: Handler = async (request, env, ctx, cache) => { } const url = parseUrl(request); + if (url === undefined) { return responses.BAD_REQUEST; } @@ -50,11 +52,14 @@ const getHandler: Handler = async (request, env, ctx, cache) => { // Cache response if cache is enabled if (shouldServeCache && response.status !== 304 && response.status !== 206) { const cached = response.clone(); + cached.headers.append('x-cache-status', 'hit'); + ctx.waitUntil(cache.put(request, cached)); } response.headers.append('x-cache-status', 'miss'); + return response; }; diff --git a/src/handlers/helpers/cachePurge.ts b/src/handlers/helpers/cachePurge.ts index b49df43..fc633aa 100644 --- a/src/handlers/helpers/cachePurge.ts +++ b/src/handlers/helpers/cachePurge.ts @@ -6,6 +6,7 @@ import { mapBucketPathToUrlPath } from '../../util'; const CachePurgeBodySchema = z.object({ paths: z.array(z.string()), }); + type CachePurgeBody = z.infer; /** @@ -21,6 +22,7 @@ async function parseBody(request: Request): Promise { } let bodyObject: object; + try { // Parse body to an object bodyObject = await request.json(); @@ -31,6 +33,7 @@ async function parseBody(request: Request): Promise { // Validate the body's contents const parseResult = CachePurgeBodySchema.safeParse(bodyObject); + if (!parseResult.success) { return responses.BAD_REQUEST; } @@ -53,11 +56,13 @@ export async function cachePurge( env: Env ): Promise { const providedApiKey = request.headers.get('x-api-key'); + if (providedApiKey !== env.CACHE_PURGE_API_KEY) { return new Response(undefined, { status: 403 }); } const body = await parseBody(request); + if (body instanceof Response) { return body; } @@ -67,17 +72,20 @@ export async function cachePurge( // https://nodejs.org. For dev, it might be // http://localhost:8787 const baseUrl = `${url.protocol}//${url.host}`; - const promises = new Array>(); + for (const path of body.paths) { const urlPaths = mapBucketPathToUrlPath(path, env); + if (typeof urlPaths === 'undefined') { continue; } + for (const urlPath of urlPaths) { promises.push(cache.delete(new Request(`${baseUrl}/${urlPath}`))); } } + await Promise.allSettled(promises); return new Response(undefined, { status: 204 }); diff --git a/src/handlers/helpers/directory.ts b/src/handlers/helpers/directoryListing.ts similarity index 71% rename from src/handlers/helpers/directory.ts rename to src/handlers/helpers/directoryListing.ts index e129775..0ae8b9a 100644 --- a/src/handlers/helpers/directory.ts +++ b/src/handlers/helpers/directoryListing.ts @@ -1,7 +1,45 @@ +import handlebars from 'handlebars'; import { Env } from '../../env'; import responses from '../../responses'; import { niceBytes } from '../../util'; -import { getFile } from './file'; +import { getFile } from './serveFile'; + +// Compiles the Handlebars Template for Directory Listing +const directoryListingTemplate = handlebars.compile(` + + + Index of {{pathname}} + + + + + +

Index of {{pathname}}

+ + + {{#each entries}} + + + + + + {{/each}} +
FilenameModifiedSize
{{name}}{{lastModified}}{{size}}
+ +`); + +type DirectoryListingEntry = { + href: string; + name: string; + lastModified: string; + size: string; +}; /** * Renders the html for a single listing entry @@ -11,17 +49,18 @@ import { getFile } from './file'; * @param size Size of the file, omit if directory * @returns Rendered html */ -function renderTableElement( +function getTableEntry( href: string, name: string, lastModified: string = '-', size: string = '-' -): string { - return ` - ${name} - ${lastModified} - ${size} - `; +): DirectoryListingEntry { + return { + href, + name, + lastModified, + size, + }; } type DirectoryListingResponse = { @@ -30,6 +69,8 @@ type DirectoryListingResponse = { }; /** + * @TODO: Simplify the iteration logic or make it more readable + * * Renders the html for a directory listing response * @param url Parsed url of the request * @param bucketPath Path in R2 bucket @@ -45,8 +86,9 @@ function renderDirectoryListing( ): DirectoryListingResponse { // Holds all the html for each directory and file // we're listing - const tableElements = new Array(); - tableElements.push(renderTableElement('../', '../')); + const tableElements = new Array(); + + tableElements.push(getTableEntry('../', '../')); const urlPathname = url.pathname.endsWith('/') ? url.pathname @@ -56,17 +98,20 @@ function renderDirectoryListing( for (const directory of delimitedPrefixes) { // R2 sends us back the absolute path of the directory, cut it const name = directory.substring(bucketPath.length); - const elementHtml = renderTableElement( + + const elementHtml = getTableEntry( `${urlPathname}${encodeURIComponent( name.substring(0, name.length - 1) )}/`, name ); + tableElements.push(elementHtml); } // Render files second let lastModified: Date | undefined = undefined; + for (const object of objects) { // R2 sends us back the absolute path of the object, cut it const name = object.key.substring(bucketPath.length); @@ -79,52 +124,25 @@ function renderDirectoryListing( } let dateStr = object.uploaded.toISOString(); + dateStr = dateStr.split('.')[0].replace('T', ' '); dateStr = dateStr.slice(0, dateStr.lastIndexOf(':')) + 'Z'; - const elementHtml = renderTableElement( + const elementHtml = getTableEntry( `${urlPathname}${encodeURIComponent(name)}`, name, dateStr, niceBytes(object.size) ); + tableElements.push(elementHtml); } return { - html: ` - - - Index of ${url.pathname} - - - - - -

Index of ${url.pathname}

- - - ${tableElements.join('\n')} -
FilenameModifiedSize
- - `, + html: directoryListingTemplate({ + pathname: url.pathname, + entries: tableElements, + }), lastModified: (lastModified ?? new Date()).toUTCString(), }; } @@ -150,12 +168,14 @@ export async function listDirectory( const objects = new Array(); let truncated = true; let cursor: string | undefined; + while (truncated) { const result = await env.R2_BUCKET.list({ prefix: bucketPath, delimiter: '/', cursor, }); + result.delimitedPrefixes.forEach(prefix => delimitedPrefixes.add(prefix)); for (const object of result.objects) { @@ -163,6 +183,7 @@ export async function listDirectory( if (object.key.endsWith('index.html')) { return getFile(url, request, bucketPath + 'index.html', env); } + objects.push(object); } @@ -182,6 +203,7 @@ export async function listDirectory( delimitedPrefixes, objects ); + return new Response(request.method === 'GET' ? response.html : null, { headers: { 'last-modified': response.lastModified, diff --git a/src/handlers/helpers/file.ts b/src/handlers/helpers/serveFile.ts similarity index 80% rename from src/handlers/helpers/file.ts rename to src/handlers/helpers/serveFile.ts index 6956946..d7e4424 100644 --- a/src/handlers/helpers/file.ts +++ b/src/handlers/helpers/serveFile.ts @@ -20,6 +20,7 @@ function getStatusCode(request: Request, objectHasBody: boolean): number { // only part of the object return 206; } + // We have the full object body return 200; } @@ -50,20 +51,25 @@ export async function getFile( env: Env ): Promise { let file: R2Object | null = null; - if (request.method === 'GET') { - try { - file = await env.R2_BUCKET.get(bucketPath, { - onlyIf: request.headers, - range: request.headers, - }); - } catch (e) { - // Unquoted etags make R2 api throw an error - return responses.BAD_REQUEST; - } - } else if (request.method === 'HEAD') { - file = await env.R2_BUCKET.head(bucketPath); - } else { - return responses.METHOD_NOT_ALLOWED; + + switch (request.method) { + case 'GET': + try { + file = await env.R2_BUCKET.get(bucketPath, { + onlyIf: request.headers, + range: request.headers, + }); + + break; + } catch (e) { + // Unquoted etags make R2 api throw an error + return responses.BAD_REQUEST; + } + case 'HEAD': + file = await env.R2_BUCKET.head(bucketPath); + break; + default: + return responses.METHOD_NOT_ALLOWED; } if (file === null) { @@ -71,8 +77,10 @@ export async function getFile( } const hasBody = objectHasBody(file); + const cacheControl = file.httpMetadata?.cacheControl ?? (env.FILE_CACHE_CONTROL || 'no-store'); + return new Response( hasBody && file.size != 0 ? (file as R2ObjectBody).body : null, { @@ -80,17 +88,16 @@ export async function getFile( headers: { etag: file.httpEtag, 'accept-range': 'bytes', - + // @TODO: Explain why JSON files can be accessed anywhere 'access-control-allow-origin': url.pathname.endsWith('.json') ? '*' : '', - 'cache-control': cacheControl, expires: file.httpMetadata?.cacheExpiry?.toUTCString() ?? '', - 'last-modified': file.uploaded.toUTCString(), 'content-encoding': file.httpMetadata?.contentEncoding ?? '', 'content-type': + // @TODO: Explain why defaults to Octet Stream file.httpMetadata?.contentType ?? 'application/octet-stream', 'content-language': file.httpMetadata?.contentLanguage ?? '', 'content-disposition': file.httpMetadata?.contentDisposition ?? '', diff --git a/src/handlers/post.ts b/src/handlers/post.ts index 0b48567..f4dd156 100644 --- a/src/handlers/post.ts +++ b/src/handlers/post.ts @@ -5,6 +5,7 @@ import { cachePurge } from './helpers/cachePurge'; const postHandler: Handler = async (request, env, _, cache) => { const url = parseUrl(request); + if (url === undefined) { return responses.BAD_REQUEST; } diff --git a/src/util.ts b/src/util.ts index 472ccb3..6f98090 100644 --- a/src/util.ts +++ b/src/util.ts @@ -26,7 +26,8 @@ export function isCacheEnabled(env: Env): boolean { * response otherwise */ export function parseUrl(request: Request): URL | undefined { - let url: URL | undefined = undefined; + let url: URL | undefined; + try { url = new URL(request.url); } catch (e) { @@ -57,9 +58,12 @@ export function mapUrlPathToBucketPath( }; // Example: /docs/asd/123 - let bucketPath: string | undefined = undefined; + let bucketPath: string | undefined; + const splitPath = url.pathname.split('/'); // ['', 'docs', 'asd', '123'] + const basePath = splitPath[1]; // 'docs' + if (basePath in urlToBucketPathMap) { bucketPath = urlToBucketPathMap[basePath]; } else if (env.DIRECTORY_LISTING !== 'restricted') { @@ -80,6 +84,7 @@ export function mapBucketPathToUrlPath( bucketPath: string, env: Pick ): string[] | undefined { + // @TODO: Use a switch statement or a Design Pattern here if (bucketPath.startsWith(DIST_PATH_PREFIX)) { const path = bucketPath.substring(15); return [`/dist${path}`, `/download/releases${path}`]; @@ -115,7 +120,7 @@ export function isDirectoryPath(path: string): boolean { } /** - * Converts raw size into readable bytes + * Converts raw size into human readable bytes * @param bytes Bytes * @returns Something like `4.5 KB` or `8.7 MB` */ diff --git a/tests/README.md b/tests/README.md index 7f17be0..c34c68b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,2 +1,3 @@ # Tests + Tests use Node's builtin test runner. See [https://nodejs.org/api/test.html](https://nodejs.org/api/test.html) for documentation. diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 85e5035..488d730 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -1,4 +1,5 @@ # End-to-End Tests + These are for testing the entire worker from client to server. The [Miniflare 3 API](https://latest.miniflare.dev) is used to spin up a local [Workerd](https://github.com/cloudflare/workerd) instance that the tests interact with. @@ -7,10 +8,12 @@ Test file names correspond to what's being tested. For example, [./directory.tes contains tests related to directory listing or directories as a whole. ## Adding a New Test File + Create the file and name it to something that's fitting to what it will be testing. For simplicity sakes, take a look at the already existing test files and copy the structure. Make sure to import the new test file into [./index.test.ts](./index.test.ts). ## Adding a New Test + Each E2E test has one big `describe` call that contains all of its tests, add tests in that. We usually prefer the `it` alias for defining tests. diff --git a/tests/e2e/directory.test.ts b/tests/e2e/directory.test.ts index 39b730f..2b0cf16 100644 --- a/tests/e2e/directory.test.ts +++ b/tests/e2e/directory.test.ts @@ -27,7 +27,7 @@ describe('Directory Tests (Restricted Directory Listing)', () => { it('allows `/dist` and returns expected html', async () => { const [res, expectedHtml] = await Promise.all([ mf.dispatchFetch(`${url}dist`), - readFile('./tests/e2e/test-data/expected-html/dist.html', { + readFile('./tests/e2e/test-data/expected-html/dist.hbs', { encoding: 'utf-8', }), ]); diff --git a/tests/e2e/test-data/R2_BUCKET/README.md b/tests/e2e/test-data/R2_BUCKET/README.md index 3f7dbe1..338fdcf 100644 --- a/tests/e2e/test-data/R2_BUCKET/README.md +++ b/tests/e2e/test-data/R2_BUCKET/README.md @@ -1,2 +1,3 @@ # R2_BUCKET + This is a local mock of a R2 bucket with the dist folder's contents. diff --git a/tests/e2e/test-data/expected-html/README.md b/tests/e2e/test-data/expected-html/README.md index 6c15655..e6e09c2 100644 --- a/tests/e2e/test-data/expected-html/README.md +++ b/tests/e2e/test-data/expected-html/README.md @@ -1,2 +1,3 @@ # expected-html + Expected html for listing responses. These should be 1:1 to what the worker returns diff --git a/tests/e2e/test-data/expected-html/dist.hbs b/tests/e2e/test-data/expected-html/dist.hbs new file mode 100644 index 0000000..94e8e06 --- /dev/null +++ b/tests/e2e/test-data/expected-html/dist.hbs @@ -0,0 +1,39 @@ + + + Index of /dist + + + + + +

Index of /dist

+ + + + + + + + + + + + + + + + + + + + + +
FilenameModifiedSize
../--
latest/--
index.json2023-09-12 05:43Z18 B
+ + diff --git a/tests/e2e/test-data/expected-html/dist.html b/tests/e2e/test-data/expected-html/dist.html deleted file mode 100644 index 518ace1..0000000 --- a/tests/e2e/test-data/expected-html/dist.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - Index of /dist - - - - - -

Index of /dist

- - - - - - - - - - - - - - - - - -
FilenameModifiedSize
../--
latest/--
index.json2023-09-12 05:43Z18 B
- - \ No newline at end of file diff --git a/tests/unit/README.md b/tests/unit/README.md index f0cd42c..2127a5e 100644 --- a/tests/unit/README.md +++ b/tests/unit/README.md @@ -1,20 +1,26 @@ # Unit Tests + These are for testing specific functions in the worker. Test file names correspond to the file name in the [`src`](../../src) directory that is being tested. For example, [`./util.test.ts`](./util.test.ts) tests functions defined from [`src/util.ts`](../../src/util.ts). ## Adding a New Test File + Create the file and name it the same name as the file in the [`src`](../../src) directory. See [Adding a New Test](#adding-a-new-test) for testing the functions. Make sure to import the new test file into [./index.test.ts](./index.test.ts). ## Adding a New Test + Each function has its tests wrapped in a `describe` call just for neatness. We usually prefer the `it` alias for defining tests. For example, tests for the function `doSomething` would look something like this: + ```js describe('doSomething', () => { - it('does something', () => {/*...*/}); + it('does something', () => { + /*...*/ + }); }); ```