├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .markdownlint.yml ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE_APACHE ├── LICENSE_MIT ├── README.md ├── RELEASE_CHECKLIST.md ├── package-lock.json ├── package.json ├── src ├── index.ts ├── mocks.ts ├── test │ ├── getAssetFromKV-optional.ts │ ├── getAssetFromKV.ts │ ├── mapRequestToAsset.ts │ └── serveSinglePageApp.ts └── types.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | indent_style = tab 7 | tab_width = 2 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | * text eol=lf -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | * @kristianfreeman @rickyrobinett @lauragift21 @LoganGrasby @craigsdennis 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | prettier: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use Node.js 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: '18.x' 14 | - name: Restore NPM cache 15 | uses: actions/cache@v3 16 | continue-on-error: true 17 | with: 18 | path: ~/.npm 19 | key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} 20 | restore-keys: | 21 | ${{ runner.os }}-node- 22 | - run: npm ci 23 | - run: npm run lint:code 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run npm tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [18.x, 20.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm ci 23 | npm run build --if-present 24 | npm test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | # MD001 Header levels should only increment by one level at a time 2 | MD001: false 3 | 4 | # MD004 Unordered list style 5 | MD004: false 6 | 7 | # MD005 Inconsistent indentation for list items at the same level 8 | MD005: false 9 | 10 | # MD006 Consider starting bulleted lists at the beginning of the line 11 | MD006: false 12 | 13 | # MD007 Unordered list indentation 14 | MD007: false 15 | 16 | # MD013 Line length 17 | MD013: false 18 | 19 | # MD024 Multiple headers with the same content 20 | MD024: false 21 | 22 | # MD025 Multiple top level headers in the same document 23 | MD025: false 24 | 25 | # MD032 Lists should be surrounded by blank lines 26 | MD032: false 27 | 28 | # MD033 Inline HTML 29 | MD033: false 30 | 31 | # MD040 Fenced code blocks should have a language specified 32 | MD040: false 33 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | package.json 3 | package-lock.json 4 | dist/**/* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "useTabs": true, 6 | "semi": false, 7 | "printWidth": 100 8 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.1 4 | 5 | - ## Maintenance 6 | 7 | - **Remove tests from npm package to reduce npm package size - [boidolr], [pull/388]** 8 | 9 | This PR removes the tests from the npm package, reducing the size of the package by about half. 10 | 11 | [boidolr]: https://github.com/boidolr 12 | [pull/388]: https://github.com/cloudflare/kv-asset-handler/pull/388 13 | 14 | - **Bump dependencies - [Cherry], [pull/367] [pull/361] [pull/362] [pull/359] [pull/390]** 15 | 16 | These PRs bump dependencies of the project to their latest versions. 17 | 18 | [pull/367]: https://github.com/cloudflare/kv-asset-handler/pull/367 19 | [pull/361]: https://github.com/cloudflare/kv-asset-handler/pull/361 20 | [pull/362]: https://github.com/cloudflare/kv-asset-handler/pull/362 21 | [pull/359]: https://github.com/cloudflare/kv-asset-handler/pull/359 22 | [pull/390]: https://github.com/cloudflare/kv-asset-handler/pull/390 23 | 24 | - **Fix README anchor links - [johtso] [pull/372]** 25 | 26 | This PR fixes the anchor links in the README. 27 | 28 | [johtso]: https://github.com/johtso 29 | [pull/372]: https://github.com/cloudflare/kv-asset-handler/pull/372 30 | 31 | ## 0.3.0 32 | 33 | - ### Features 34 | 35 | - **Allow configurable downgrade of ETag validator strength - [awwong1], [pull/315]** 36 | 37 | This allows users to override the default strong ETag validator behaviour to use weak ETag validators. This change allows the developer to use weak ETags and preserve 304 responses (e.g. on *.workers.dev domains). 38 | 39 | - ### Fixes 40 | 41 | - **Fix length property call on ArrayBuffer instance - [philipatkinson], [pull/295**] 42 | 43 | Previously when edge cached was enabled, the `content-length` of the response was not being set correctly. This was due to the `length` property of the `ArrayBuffer` instance being called instead of the `byteLength` property. This PR fixes this issue. 44 | 45 | - ### Maintenance 46 | 47 | - **chore(ci): bump node versions in actions - [KianNH], [pull/354]** 48 | 49 | This bumps the Node versions used in the CI actions to the latest LTS versions. 50 | 51 | - **chore: use tabs for indentation - [Cherry], [pull/355]** 52 | 53 | This PR changes the indentation of the project to use tabs instead of spaces, falling more in line with other Cloudflare JavaScript projects like wrangler. 54 | 55 | - **chore: bump dependencies - [Cherry], [pull/356]** 56 | 57 | This bumps many dependencies of the project to their latest versions. 58 | 59 | 60 | ## 0.2.0 61 | 62 | - ### Features 63 | 64 | - **Allow changing pathIsEncoded through options - [JackPriceBurns], [pull/243]** 65 | 66 | When using `mapRequestToAsset`, it encodes the URL / key and will never check the KV store for the decoded key. 67 | 68 | This adds the ability to set `pathIsEncoded` to true, which will decode the URL before getting it from the KV. 69 | 70 | [jackpriceburns]: https://github.com/JackPriceBurns 71 | [pull/243]: https://github.com/cloudflare/kv-asset-handler/pull/243 72 | 73 | - **Support ES Modules. - [threepointone], [pull/261]** 74 | 75 | This PR provides a possible solution for getting Workers Sites working with ES Module workers. This approach is not as invasive as other approaches, so isn't as risky either. 76 | 77 | Usage: 78 | 79 | ```jsx 80 | import manifestJSON from "__STATIC_CONTENT_MANIFEST"; 81 | const manifest = JSON.parse(manifestJSON); 82 | 83 | export default { 84 | fetch(request, env, ctx) { 85 | return await getAssetFromKV( 86 | { 87 | request, 88 | waitUntil(promise) { 89 | return ctx.waitUntil(promise); 90 | }, 91 | }, 92 | { 93 | ASSET_NAMESPACE: env.ASSET_NAMESPACE, 94 | ASSET_MANIFEST: manifest, 95 | } 96 | ); 97 | // ... 98 | }, 99 | }; 100 | ``` 101 | 102 | [threepointone]: https://github.com/threepointone 103 | [pull/261]: https://github.com/cloudflare/kv-asset-handler/pull/261 104 | 105 | - ### Fixes 106 | 107 | - **fix: default ASSET_MANIFEST to empty object - [Cherry], [pull/254]** 108 | 109 | As per [discussion in Discord](https://canary.discord.com/channels/595317990191398933/831143699999752262/898392183999197184) and the repo at [https://github.com/Erisa-bits/getassetfromkv-undefined-error], allowing `ASSET_MANIFEST` to be optional got lost somewhere along the years and errors when attempted to be used without it. This PR restores this functionality by setting it to an empty object (instead of `undefined`), which allows fall-through to the standard `mapRequestToAsset` function. 110 | 111 | chore: bump dependencies - This updates a few dependencies and also pins `@types/node` to `15.x` since `16.x` has some incompatible types. 112 | feat: generate more modern code - This removes the unnecessary async/await polyfill added by TypeScript 113 | 114 | [cherry]: https://github.com/Cherry 115 | [pull/254]: https://github.com/cloudflare/kv-asset-handler/pull/254 116 | 117 | - ### Maintenance 118 | 119 | - **chore: remove debug logs around `response.body.cancel` support - [Cherry], [pull/249]** 120 | 121 | Fixes [issues/248] 122 | 123 | [cherry]: https://github.com/Cherry 124 | [pull/249]: https://github.com/cloudflare/kv-asset-handler/pull/249 125 | [issues/248]: https://github.com/cloudflare/kv-asset-handler/issue/248 126 | 127 | ## 0.1.3 128 | 129 | - ### Performance 130 | 131 | - **Only parse `ASSET_MANIFEST` once on startup - [Cherry], [pull/185]** 132 | 133 | This PR improves performance of the `getAssetFromKV` function by only parsing the asset manifest once on startup, instead of on each request. This can have a significant improvement in response times for larger sites. An example of the performance improvement with an asset manifest of over 50k files: 134 | 135 | > Before change: 136 | 100 iterations: Done. Mean kv response time is 16.61 137 | 1000 iterations: Done. Mean kv response time is 17.798 138 | > After change: 139 | 100 iterations: Done. Mean kv response time is 6.62 140 | 1000 iterations: Done. Mean kv response time is 7.296 141 | 142 | Initial work and credit to [groenlid] in [pull/143]. 143 | 144 | [Cherry]: https://github.com/Cherry 145 | [groenlid]: https://github.com/groenlid 146 | [pull/185]: https://github.com/cloudflare/kv-asset-handler/pull/185 147 | [pull/143]: https://github.com/cloudflare/kv-asset-handler/pull/143 148 | 149 | - ### Fixes 150 | 151 | - **ESM compatibility: fix crash on missing global environment variables - [ttraenkler], [pull/188]** 152 | 153 | This PR fixes the library from crashing when global environment variables such as `__STATIC_CONTENT` and `__STATIC_CONTENT_MANIFEST` are missing, which is currently the case when using the new ESM module syntax. 154 | 155 | Note that whilst this partially resolves the issue discussed in [issue/174], it does not provide full ESM compatibility yet. Please see [issue/174] for further discussion. 156 | 157 | [ttraenkler]: https://github.com/ttraenkler 158 | [pull/188]: https://github.com/cloudflare/kv-asset-handler/pull/188 159 | [issue/174]: https://github.com/cloudflare/kv-asset-handler/issues/174 160 | 161 | - ### Maintenance 162 | 163 | - **Tweak GitHub Actions Workflow for proper PR testing - [Cherry], [pull/185]** 164 | 165 | This PR tweaks the GitHub Actions Workflow to test PRs properly, both in terms of linting and the repository tests. It runs `prettier` to maintain code quality and style, and all unit tests on every PR to ensure no regressions occur. 166 | 167 | [pull/183]: https://github.com/cloudflare/kv-asset-handler/pull/185 168 | [Cherry]: https://github.com/Cherry 169 | 170 | - **Add test for `mapRequestToAsset` asset override - [Cherry], [pull/186]** 171 | 172 | This PR adds a test for the functionality added in [pull/159]. This tests that when overriding the `mapRequestToAsset` function in its entirety, this function is always run. 173 | 174 | [pull/159]: https://github.com/cloudflare/kv-asset-handler/pull/159 175 | [pull/186]: https://github.com/cloudflare/kv-asset-handler/pull/186 176 | [Cherry]: https://github.com/Cherry 177 | 178 | - **Dependabot updates** 179 | 180 | A number of dependabot patch-level updates have been merged: 181 | 182 | - Bump @types/node from 15.3.1 to 15.6.0 ([pull/183]) 183 | - Bump @types/node from 15.6.0 to 15.6.1 ([pull/184]) 184 | - Bump @types/node from 15.6.1 to 15.9.0 ([pull/189]) 185 | - Bump @types/node from 15.9.0 to 15.12.0 ([pull/190]) 186 | - Bump @types/node from 15.12.0 to 15.12.1 ([pull/191]) 187 | - Bump @types/node from 15.12.1 to 15.12.2 ([pull/193]) 188 | - Bump typescript from 4.2.4 to 4.3.2 ([pull/187]) 189 | - Bump prettier from 2.3.0 to 2.3.1 ([pull/192]) 190 | 191 | [pull/183]: https://github.com/cloudflare/kv-asset-handler/pull/183 192 | [pull/184]: https://github.com/cloudflare/kv-asset-handler/pull/184 193 | [pull/189]: https://github.com/cloudflare/kv-asset-handler/pull/189 194 | [pull/190]: https://github.com/cloudflare/kv-asset-handler/pull/190 195 | [pull/191]: https://github.com/cloudflare/kv-asset-handler/pull/191 196 | [pull/193]: https://github.com/cloudflare/kv-asset-handler/pull/193 197 | [pull/187]: https://github.com/cloudflare/kv-asset-handler/pull/187 198 | [pull/192]: https://github.com/cloudflare/kv-asset-handler/pull/192 199 | 200 | ## 0.1.2 201 | 202 | - ### Features 203 | 204 | - **Support for `defaultDocument` configuration - [boemekeld], [pull/161]** 205 | 206 | This PR adds support for customizing the `defaultDocument` option in `getAssetFromKV`. In situations where a project does not use `index.html` as the default document for a path, this can now be customized to values like `index.shtm`: 207 | 208 | ```js 209 | return getAssetFromKV(event, { 210 | defaultDocument: "index.shtm" 211 | }) 212 | ``` 213 | 214 | [boemekeld]: https://github.com/boemekeld 215 | [pull/161]: https://github.com/cloudflare/kv-asset-handler/pull/161 216 | 217 | - ### Fixes 218 | 219 | - **Fire `mapRequestToAsset` for all requests, if explicitly defined - [Cherry], [pull/159]** 220 | 221 | This PR fixes an issue where a custom `mapRequestToAsset` handler weren't fired if a matching asset path was found in `ASSET_MANIFEST` data. By correctly checking for this handler, we can conditionally handle any assets with this handler _even_ if they exist in the `ASSET_MANIFEST`. 222 | 223 | **Note that this is a breaking change**, as previously, the mapRequestToAsset function was ignored if you set it, and an exact match was found in the `ASSET_MANIFEST`. That being said, this behavior was a bug, and unexpected behavior, as documented in [issue/158]. 224 | 225 | [Cherry]: https://github.com/Cherry 226 | [issue/158]: https://github.com/cloudflare/kv-asset-handler/pull/158 227 | [pull/159]: https://github.com/cloudflare/kv-asset-handler/pull/159 228 | 229 | - **Etag logic refactor - [shagamemnon], [pull/133]** 230 | 231 | This PR refactors a great deal of the Etag functionality introduced in [0.0.11](https://github.com/cloudflare/kv-asset-handler/milestone/7?closed=1). `kv-asset-handler` will now correctly set [strong and weak Etags](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) both to the Cloudflare CDN and to client eyeballs, allowing for higher cache percentages with Workers Sites projects. 232 | 233 | [pull/133]: https://github.com/cloudflare/kv-asset-handler/pull/133 234 | [shagamemnon]: https://github.com/shagamemnon 235 | 236 | - **Fix path decoding issue - [xiaolanglanglang], [pull/142]** 237 | 238 | This PR improves support for non-alphanumeric character paths in `kv-asset-handler`, for instance, if the path requested is in Chinese. 239 | 240 | [xiaolanglanglang]: https://github.com/xiaolanglanglang 241 | [pull/142]: https://github.com/cloudflare/kv-asset-handler/pull/142 242 | 243 | - **Check HTTP method after mapRequestToAsset - [oliverpool], [pull/178]** 244 | 245 | This PR fixes an issue where the HTTP method for an asset is checked before the `mapRequestToAsset` handler is called. This has caused issues for users in the past, where they need to generate a `requestKey` based on an asset path, even if the request method is not `GET`. This fixes [issue/151]. 246 | 247 | [oliverpool]: https://github.com/oliverpool 248 | [pull/178]: https://github.com/cloudflare/kv-asset-handler/pull/178 249 | [issue/151]: https://github.com/cloudflare/kv-asset-handler/issues/151 250 | 251 | - ### Maintenance 252 | 253 | - **Add Markdown linting workflow to GitHub Actions - [jbampton], [pull/135]** 254 | 255 | Our GitHub Actions workflow now includes a linting workflow for Markdown in the project, including the README, this CHANGELOG, and any other `.md` files in the source code. 256 | 257 | [jbampton]: https://github.com/jbampton 258 | [pull/135]: https://github.com/cloudflare/kv-asset-handler/pull/135 259 | 260 | - **Dependabot updates** 261 | 262 | A number of dependabot patch-level updates have been merged since our last release: 263 | 264 | - Bump @types/node from 15.30.0 to 15.30.1 ([pull/180]) 265 | - Bump hosted-git-info from 2.8.8 to 2.8.9 ([pull/176]) 266 | - Bump ini from 1.3.5 to 1.3.8 ([pull/160]) 267 | - Bump lodash from 4.17.19 to 4.17.21 ([pull/175]) 268 | - Bump urijs from 1.19.2 to 1.19.6 ([pull/168]) 269 | - Bump y18n from 4.0.0 to 4.0.1 ([pull/173]) 270 | 271 | [pull/160]: https://github.com/cloudflare/kv-asset-handler/pull/160 272 | [pull/168]: https://github.com/cloudflare/kv-asset-handler/pull/168 273 | [pull/173]: https://github.com/cloudflare/kv-asset-handler/pull/173 274 | [pull/175]: https://github.com/cloudflare/kv-asset-handler/pull/175 275 | [pull/176]: https://github.com/cloudflare/kv-asset-handler/pull/176 276 | [pull/180]: https://github.com/cloudflare/kv-asset-handler/pull/180 277 | 278 | - **Repository maintenance - [Cherry], [pull/179]** 279 | 280 | New project maintainer Cherry did a ton of maintenance in this release, improving workflows, code quality, and more. Check out the full list in [the PR][pull/179]. 281 | 282 | [Cherry]: https://github.com/Cherry 283 | [pull/179]: https://github.com/cloudflare/kv-asset-handler/pull/179 284 | 285 | - ### Documentation 286 | 287 | - **Update README.md - [signalnerve], [pull/177]** 288 | 289 | This PR adds context to our README, with mentions about _what_ this project is, how to use it, and some new things since the last version of this package: namely, [Cloudflare Pages](https://pages.dev) and the new [Cloudflare Workers Discord server](https://discord.gg/cloudflaredev) 290 | 291 | [signalnerve]: https://github.com/signalnerve 292 | [pull/177]: https://github.com/cloudflare/kv-asset-handler/pull/177 293 | 294 | - **Add instructions for updating version in related repos - [caass], [pull/171]** 295 | 296 | This PR adds instructions for updating the `kv-asset-handler` version in related repositories, such as our templates, that use `kv-asset-handler` and are exposed to end-users of Wrangler and Workers. 297 | 298 | [caass]: https://github.com/caass 299 | [pull/177]: https://github.com/cloudflare/kv-asset-handler/pull/171 300 | 301 | ## 0.1.1 302 | 303 | - ### Fixes 304 | 305 | - **kv-asset-handler can translate 206 responses to 200 - [harrishancock], [pull/166]** 306 | 307 | Fixes [wrangler#1746](https://github.com/cloudflare/wrangler/issues/1746) 308 | 309 | [harrishancock](https://github.com/harrishancock) 310 | [pull/166](https://github.com/cloudflare/kv-asset-handler/pull/166) 311 | 312 | ## 0.0.12 313 | 314 | - ### Features 315 | 316 | - **Add defaultMimeType option to getAssetFromKV - [mgrahamjo], [pull/121]** 317 | 318 | Some static website owners prefer not to create all of their web routes as directories containing index.html files. Instead, they prefer to create pages as extensionless HTML files. Providing a defaultMimeType option will allow users to set the Content-Type header for extensionless files to text/html, which will enable this use case. 319 | 320 | [mgrahamjo]: https://github.com/mgrahamjo 321 | [pull/121]: https://github.com/cloudflare/kv-asset-handler/pull/121 322 | 323 | - **Add defaultMimeType to types - [shagamemnon], [pull/132]** 324 | 325 | Adds the newly added defaultMimeType to the exported types for this package. 326 | 327 | [pull/132]: https://github.com/cloudflare/kv-asset-handler/pull/132 328 | 329 | - ### Fixes 330 | 331 | - **Fix text/* charset - [EatonZ], [pull/130]** 332 | 333 | Adds a missing `-` to the `utf-8` charset value in response mime types. 334 | 335 | [EatonZ]: https://github.com/EatonZ 336 | [pull/130]: https://github.com/cloudflare/kv-asset-handler/pull/130 337 | 338 | - **Cache handling for HEAD requests - [klittlepage], [pull/141]** 339 | 340 | This PR skips caching for incoming HEAD requests, as they should not be able to be edge cached. 341 | 342 | [klittlepage]: https://github.com/klittlepage 343 | [pull/141]: https://github.com/cloudflare/kv-asset-handler/pull/141 344 | 345 | - ### Maintenance 346 | 347 | - **Markdown linting/typos - [jbampton], [pull/123], [pull/125], [pull/126], [pull/127], [pull/128], [pull/129], [pull/131], [pull/134]** 348 | 349 | These PRs contain various typo fixes and linting of existing Markdown files in our documentation and CHANGELOG. 350 | 351 | [jbampton]: https://github.com/jbampton 352 | [pull/123]: https://github.com/cloudflare/kv-asset-handler/pull/123 353 | [pull/125]: https://github.com/cloudflare/kv-asset-handler/pull/125 354 | [pull/126]: https://github.com/cloudflare/kv-asset-handler/pull/126 355 | [pull/127]: https://github.com/cloudflare/kv-asset-handler/pull/127 356 | [pull/128]: https://github.com/cloudflare/kv-asset-handler/pull/128 357 | [pull/129]: https://github.com/cloudflare/kv-asset-handler/pull/129 358 | [pull/131]: https://github.com/cloudflare/kv-asset-handler/pull/131 359 | [pull/134]: https://github.com/cloudflare/kv-asset-handler/pull/134 360 | 361 | ## 0.0.11 362 | 363 | - ### Features 364 | 365 | - **Support cache revalidation using ETags and If-None-Match - [shagamemnon], [issue/62] [pull/94] [pull/113]** 366 | 367 | Previously, cacheable resources were not looked up from the browser cache because `getAssetFromKV` would never return a `304 Not Modified` response. 368 | 369 | Now, `getAssetFromKV` sets an `ETag` header on all cacheable assets before putting them in the Cache API, and therefore will return a `304` response when appropriate. 370 | 371 | [shagamemnon]: https://github.com/shagamemnon 372 | [pull/94]: https://github.com/cloudflare/kv-asset-handler/pull/94 373 | [pull/113]: https://github.com/cloudflare/kv-asset-handler/issues/113 374 | [issue/62]: https://github.com/cloudflare/kv-asset-handler/issues/62 375 | 376 | - **Export TypeScript types - [ispivey], [issue/43] [pull/106]** 377 | 378 | [ispivey]: https://github.com/ispivey 379 | [pull/106]: https://github.com/cloudflare/kv-asset-handler/pull/106 380 | [issue/43]: https://github.com/cloudflare/kv-asset-handler/issues/43 381 | 382 | - ### Fixes 383 | 384 | - **Support non-ASCII characters in paths - [SukkaW], [issue/99] [pull/105]** 385 | 386 | Fixes an issue where non-ASCII paths were not URI-decoded before being looked up, causing non-ASCII paths to 404. 387 | 388 | [SukkaW]: https://github.com/SukkaW 389 | [pull/105]: https://github.com/cloudflare/kv-asset-handler/pull/105 390 | [issue/99]: https://github.com/cloudflare/kv-asset-handler/issues/99 391 | 392 | - **Support `charset=utf8` in MIME type - [theromis], [issue/92] [pull/97]** 393 | 394 | Fixes an issue where `Content-Type: text/*` was never appended with `; charset=utf8`, meaning clients would not render non-ASCII characters properly. 395 | 396 | [theromis]: https://github.com/theromis 397 | [pull/97]: https://github.com/cloudflare/kv-asset-handler/pull/97 398 | [issue/92]: https://github.com/cloudflare/kv-asset-handler/issues/92 399 | 400 | - **Fix bugs in README examples - [kentonv] [bradyjoslin], [issue/93] [pull/102] [issue/88] [pull/116]** 401 | 402 | [kentonv]: https://github.com/kentonv 403 | [bradyjoslin]: https://github.com/bradyjoslin 404 | [pull/102]: https://github.com/cloudflare/kv-asset-handler/pull/102 405 | [pull/116]: https://github.com/cloudflare/kv-asset-handler/pull/116 406 | [issue/93]: https://github.com/cloudflare/kv-asset-handler/issues/93 407 | [issue/88]: https://github.com/cloudflare/kv-asset-handler/issues/88 408 | 409 | - ### Maintenance 410 | 411 | - **Make `@cloudflare/workers-types` a dependency and update deps - [ispivey], [pull/107]** 412 | 413 | [ispivey]: https://github.com/ispivey 414 | [pull/107]: https://github.com/cloudflare/kv-asset-handler/pull/107 415 | 416 | - **Add Code of Conduct - [EverlastingBugstopper], [pull/101]** 417 | 418 | [EverlastingBugstopper]: https://github.com/EverlastingBugstopper 419 | [pull/101]: https://github.com/cloudflare/kv-asset-handler/pull/101 420 | 421 | ## 0.0.10 422 | 423 | - ### Features 424 | 425 | - **Allow extensionless files to be served - [victoriabernard92], [cloudflare/wrangler/issues/980], [pull/73]** 426 | 427 | Prior to this PR, `getAssetFromKv` assumed extensionless requests (e.g. `/some-path`) would be set up to be served as the corresponding HTML file in storage (e.g. `some-path.html`). 428 | This fix checks the `ASSET_MANIFEST` for the extensionless file name _before_ appending the HTML extension. If the extensionless file exists (e.g. `some-path` exists as a key in the ASSET_MANIFEST) then we serve that file first. If the extensionless file does not exist, then the behavior does not change (e.g. it still looks for `some-path.html`). 429 | 430 | [victoriabernard92]: https://github.com/victoriabernard92 431 | [cloudflare/wrangler/issues/980]: https://github.com/cloudflare/wrangler/issues/980 432 | [pull/73]: https://github.com/cloudflare/kv-asset-handler/pull/73 433 | 434 | - ### Fixes 435 | 436 | - **Fix URL parsing in serveSinglePageApp - [signalnerve],[sgiacosa], [issue/72], [pull/82]** 437 | 438 | This fixes an issue in `serveSinglePageApp` where the request.url is used as a string to retrieve static content. For example, 439 | if a query parameter was set, the URL lookup would break. This fix uses a parsed URL instead of the string and adjusts the README. 440 | 441 | [signalnerve]: https://github.com/signalnerve 442 | [sgiacosa]: https://github.com/sgiacosa 443 | [issue/72]: https://github.com/cloudflare/kv-asset-handler/issue/72 444 | [pull/82]: https://github.com/cloudflare/kv-asset-handler/pull/82 445 | 446 | ## 0.0.9 447 | 448 | - ### Fixes 449 | 450 | - **Building and publishing to npm - [victoriabernard92], [pull/78], [pull/79]** 451 | 452 | Added a `prepack` step that builds JavaScript files from the TypeScript source. This fixes previously broken `npm` publishes. 453 | 454 | [victoriabernard92]: https://github.com/victoriabernard92 455 | [issue/78]: https://github.com/cloudflare/kv-asset-handler/issue/78 456 | [pull/79]: https://github.com/cloudflare/kv-asset-handler/pull/79 457 | 458 | ## 0.0.8 459 | 460 | - ### Features 461 | 462 | - **Support a variety of errors thrown from `getAssetFromKV` - [victoriabernard92], [issue/59] [pull/64]** 463 | 464 | Previously, `getAssetFromKv` would throw the same error type if anything went wrong. Now it will throw different error types so that clients can catch and differentiate them. 465 | For example, a 404 `NotFoundError` error implies nothing went wrong, the asset just didn't exist while 466 | a 500 `InternalError` means an expected variable was undefined. 467 | 468 | [victoriabernard92]: https://github.com/victoriabernard92 469 | [issue/44]: https://github.com/cloudflare/kv-asset-handler/issues/44 470 | [issue/59]: https://github.com/cloudflare/kv-asset-handler/issues/59 471 | [pull/47]: https://github.com/cloudflare/kv-asset-handler/pull/47 472 | 473 | - ### Fixes 474 | 475 | - **Range Issue with Safari and videos - [victoriabernard92], [issue/60] [pull/66]** 476 | 477 | Previously, if you wanted to serve a video from Workers KV using `kv-asset-handler`, it would be broken on Safari due to its requirement that all videos support the [`Content-Range` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range). Cloudflare already has a feature that will handle these headers automatically, we just needed to take advantage of it by passing in a `Request` object to the [Cache API](https://developers.cloudflare.com/workers/reference/apis/cache/) rather than a URL string. 478 | videos from not including the range headers. 479 | 480 | [victoriabernard92]: https://github.com/victoriabernard92 481 | [shagamemnon]: https://github.com/shagamemnon 482 | [issue/60]: https://github.com/cloudflare/kv-asset-handler/issues/60 483 | [issue/63]: https://github.com/cloudflare/kv-asset-handler/issues/63 484 | [pull/47]: https://github.com/cloudflare/kv-asset-handler/pull/52 485 | [pull/66]: https://github.com/cloudflare/kv-asset-handler/pull/66 486 | 487 | - **Support custom asset namespaces passed into `getAssetFromKV` - [victoriabernard92], [issue/67] [pull/68]** 488 | 489 | This functionality was documented but not properly supported. Tests and implementation fixes applied. 490 | 491 | [victoriabernard92]: https://github.com/victoriabernard92 492 | [issue/67]: https://github.com/cloudflare/kv-asset-handler/issues/67 493 | [pull/68]: https://github.com/cloudflare/kv-asset-handler/pull/68 494 | 495 | ## 0.0.7 496 | 497 | - ### Features 498 | 499 | - **Add handler for SPAs - [ashleymichal], [issue/46] [pull/47]** 500 | 501 | Some browser applications employ client-side routers that handle navigation in the browser rather than on the server. These applications will work as expected until a non-root URL is requested from the server. This PR adds a special handler, `serveSinglePageApp`, that maps all HTML requests to the root index.html. This is similar to setting a static asset route pattern in an Express.js app. 502 | 503 | [ashleymichal]: https://github.com/ashleymichal 504 | [issue/46]: https://github.com/cloudflare/kv-asset-handler/issues/46 505 | [pull/47]: https://github.com/cloudflare/kv-asset-handler/pull/47 506 | 507 | - ### Documentation 508 | 509 | - **Add function API for `getAssetFromKV` to README.md - [ashleymichal], [issue/48] [pull/52]** 510 | 511 | This function, used to abstract away the implementation for retrieving static assets from a Workers KV namespace, includes a lot of great options for configuring your own, bespoke "Workers Sites" implementation. This PR adds documentation to the README for use by those who would like to tinker with these options. 512 | 513 | [ashleymichal]: https://github.com/ashleymichal 514 | [issue/46]: https://github.com/cloudflare/kv-asset-handler/issues/48 515 | [pull/47]: https://github.com/cloudflare/kv-asset-handler/pull/52 516 | 517 | ## 0.0.6 518 | 519 | - ### Fixes 520 | 521 | - **Improve caching - [victoriabernard92], [issue/38] [pull/37]** 522 | 523 | - Don't use browser cache by default: Previously, `kv-asset-handler` would set a `Cache-Control` header on the response sent back from the Worker to the client. After this fix, the `Cache-Control` header will only be set if `options.cacheControl.browserTTL` is set by the caller. 524 | 525 | - Set default edge caching to 2 days: Previously the default cache time for static assets was 100 days. This PR sets the default to 2 days. This can be overridden with `options.cacheControl.edgeTTL`. 526 | 527 | [victoriabernard92]: https://github.com/victoriabernard92 528 | [issue/38]: https://github.com/cloudflare/kv-asset-handler/issues/38 529 | [pull/37]: https://github.com/cloudflare/kv-asset-handler/pull/37 530 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [wrangler@cloudflare.com](mailto:wrangler@cloudflare.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | . 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | . Translations are available at 128 | . 129 | -------------------------------------------------------------------------------- /LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Ashley Williams 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository has been archived and `@cloudflare/kv-asset-handler` has moved to [`workers-sdk`](https://github.com/cloudflare/workers-sdk). Please file any issues over in that new repository. 2 | -------------------------------------------------------------------------------- /RELEASE_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | This is a list of the things that need to happen during a release. 4 | 5 | ## Build a Release 6 | 7 | ### Prepare the Changelog 8 | 9 | 1. Open the associated milestone. All issues and PRs should be closed. If 10 | they are not you should reassign all open issues and PRs to future 11 | milestones. 12 | 1. Go through the commit history since the last release. Ensure that all PRs 13 | that have landed are marked with the milestone. You can use this to 14 | show all the PRs that are merged on or after YYY-MM-DD: 15 | `https://github.com/issues?q=repo%3Acloudflare%2Fkv-asset-handler+merged%3A%3E%3DYYYY-MM-DD` 16 | 1. Go through the closed PRs in the milestone. 17 | 1. Add this release to the `CHANGELOG.md`. Use the structure of previous 18 | entries. If you use VS Code, you can use [this snippet](https://gist.github.com/victoriabernard92/296c39721a3f4b171cb55c9ab9a65ec2) to insert new changelog sections. If it is a release candidate, no official changelog is needed, but testing instructions will be added later in the process. 19 | 20 | ### Start a release PR 21 | 22 | 1. Create a new branch "#.#.#" where "#.#.#" is this release's version (release) or "#.#.#-rc.#" (release candidate) 23 | 1. Push up a commit with the `CHANGELOG.md` changes. The commit message can just be "#.#.#" (release) or "#.#.#-rc.#" (release candidate) 24 | 1. Request review from the @cloudflare/workers-devexp team. 25 | 26 | ### Review 27 | 28 | Most of your comments will be about the changelog. Once the PR is finalized and approved... 29 | 30 | 1. If you made changes, squash or fixup all changes into a single commit. 31 | 1. Run `git push` and wait for CI to pass. 32 | 33 | ### Tag and build release 34 | 35 | 1. Once ready to merge, tag the commit by running either `git tag -a v#.#.# -m #.#.#` 36 | 1. Run `git push --tags`. 37 | 1. Wait for CI to pass. 38 | 39 | ### Edit the release 40 | 41 | Draft a new release on the [releases page](https://github.com/cloudflare/kv-asset-handler/releases) and update release notes. 42 | 43 | ### Publish to npm 44 | 45 | Full releases are tagged `latest`. If for some reason you mix up the commands below, follow the troubleshooting guide. 46 | 47 | 1. If this is a full release, `cd npm && npm publish`. If it is a release candidate, `cd npm && npm publish --tag beta` 48 | 1. Tweet. 49 | 50 | ### Update deps in related repos 51 | 52 | 1. Create a new branch in [worker-sites-init](https://github.com/cloudflare/worker-sites-init) 53 | 2. Update the version of `@cloudflare/kv-asset-handler` if necessary in `package.json` 54 | 3. Run `npm update` 55 | 4. Commit and create a PR 56 | 5. Repeat the process in [worker-sites-template](https://github.com/cloudflare/worker-sites-template) in the `workers-site` subdirectory 57 | 58 | # Troubleshooting a release 59 | 60 | Mistakes happen. Most of these release steps are recoverable if you mess up. The goal is not to, but if you find yourself cursing a fat fingered command, here are some troubleshooting tips. Please feel free to add to this guide. 61 | 62 | ## I pushed the wrong tag 63 | 64 | Tags and releases can be removed in GitHub. First, [remove the remote tag](https://stackoverflow.com/questions/5480258/how-to-delete-a-remote-tag): 65 | 66 | ```console 67 | git push --delete origin tagname 68 | ``` 69 | 70 | This will turn the release into a `draft` and you can delete it from the edit page. 71 | 72 | Make sure you also delete the local tag: 73 | 74 | ```console 75 | git tag --delete vX.X.X 76 | ``` 77 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudflare/kv-asset-handler", 3 | "version": "0.3.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@cloudflare/kv-asset-handler", 9 | "version": "0.3.1", 10 | "license": "MIT OR Apache-2.0", 11 | "dependencies": { 12 | "mime": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "@ava/typescript": "^4.1.0", 16 | "@cloudflare/workers-types": "^4.20231218.0", 17 | "@types/mime": "^3.0.4", 18 | "@types/node": "^18.11.12", 19 | "ava": "^6.0.1", 20 | "prettier": "^3.2.2", 21 | "service-worker-mock": "^2.0.5", 22 | "typescript": "^5.3.3" 23 | } 24 | }, 25 | "node_modules/@ava/typescript": { 26 | "version": "4.1.0", 27 | "resolved": "https://registry.npmjs.org/@ava/typescript/-/typescript-4.1.0.tgz", 28 | "integrity": "sha512-1iWZQ/nr9iflhLK9VN8H+1oDZqe93qxNnyYUz+jTzkYPAHc5fdZXBrqmNIgIfFhWYXK5OaQ5YtC7OmLeTNhVEg==", 29 | "dev": true, 30 | "dependencies": { 31 | "escape-string-regexp": "^5.0.0", 32 | "execa": "^7.1.1" 33 | }, 34 | "engines": { 35 | "node": "^14.19 || ^16.15 || ^18 || ^20" 36 | } 37 | }, 38 | "node_modules/@cloudflare/workers-types": { 39 | "version": "4.20231218.0", 40 | "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20231218.0.tgz", 41 | "integrity": "sha512-Vs1FKjfUjXYGbCsXzkl+ITp0Iyb6QiW6+vTERTNThC+v96T0IvPVAioH4tT20rXwoxAfxh380mAaxYtTrJUNVg==", 42 | "dev": true 43 | }, 44 | "node_modules/@mapbox/node-pre-gyp": { 45 | "version": "1.0.11", 46 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", 47 | "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", 48 | "dev": true, 49 | "dependencies": { 50 | "detect-libc": "^2.0.0", 51 | "https-proxy-agent": "^5.0.0", 52 | "make-dir": "^3.1.0", 53 | "node-fetch": "^2.6.7", 54 | "nopt": "^5.0.0", 55 | "npmlog": "^5.0.1", 56 | "rimraf": "^3.0.2", 57 | "semver": "^7.3.5", 58 | "tar": "^6.1.11" 59 | }, 60 | "bin": { 61 | "node-pre-gyp": "bin/node-pre-gyp" 62 | } 63 | }, 64 | "node_modules/@nodelib/fs.scandir": { 65 | "version": "2.1.5", 66 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 67 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 68 | "dev": true, 69 | "dependencies": { 70 | "@nodelib/fs.stat": "2.0.5", 71 | "run-parallel": "^1.1.9" 72 | }, 73 | "engines": { 74 | "node": ">= 8" 75 | } 76 | }, 77 | "node_modules/@nodelib/fs.stat": { 78 | "version": "2.0.5", 79 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 80 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 81 | "dev": true, 82 | "engines": { 83 | "node": ">= 8" 84 | } 85 | }, 86 | "node_modules/@nodelib/fs.walk": { 87 | "version": "1.2.8", 88 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 89 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 90 | "dev": true, 91 | "dependencies": { 92 | "@nodelib/fs.scandir": "2.1.5", 93 | "fastq": "^1.6.0" 94 | }, 95 | "engines": { 96 | "node": ">= 8" 97 | } 98 | }, 99 | "node_modules/@rollup/pluginutils": { 100 | "version": "4.2.1", 101 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", 102 | "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", 103 | "dev": true, 104 | "dependencies": { 105 | "estree-walker": "^2.0.1", 106 | "picomatch": "^2.2.2" 107 | }, 108 | "engines": { 109 | "node": ">= 8.0.0" 110 | } 111 | }, 112 | "node_modules/@rollup/pluginutils/node_modules/picomatch": { 113 | "version": "2.3.1", 114 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 115 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 116 | "dev": true, 117 | "engines": { 118 | "node": ">=8.6" 119 | }, 120 | "funding": { 121 | "url": "https://github.com/sponsors/jonschlinkert" 122 | } 123 | }, 124 | "node_modules/@sindresorhus/merge-streams": { 125 | "version": "1.0.0", 126 | "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", 127 | "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", 128 | "dev": true, 129 | "engines": { 130 | "node": ">=18" 131 | }, 132 | "funding": { 133 | "url": "https://github.com/sponsors/sindresorhus" 134 | } 135 | }, 136 | "node_modules/@types/mime": { 137 | "version": "3.0.4", 138 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz", 139 | "integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==", 140 | "dev": true 141 | }, 142 | "node_modules/@types/node": { 143 | "version": "18.19.7", 144 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.7.tgz", 145 | "integrity": "sha512-IGRJfoNX10N/PfrReRZ1br/7SQ+2vF/tK3KXNwzXz82D32z5dMQEoOlFew18nLSN+vMNcLY4GrKfzwi/yWI8/w==", 146 | "dev": true, 147 | "dependencies": { 148 | "undici-types": "~5.26.4" 149 | } 150 | }, 151 | "node_modules/@vercel/nft": { 152 | "version": "0.24.4", 153 | "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.24.4.tgz", 154 | "integrity": "sha512-KjYAZty7boH5fi5udp6p+lNu6nawgs++pHW+3koErMgbRkkHuToGX/FwjN5clV1FcaM3udfd4zW/sUapkMgpZw==", 155 | "dev": true, 156 | "dependencies": { 157 | "@mapbox/node-pre-gyp": "^1.0.5", 158 | "@rollup/pluginutils": "^4.0.0", 159 | "acorn": "^8.6.0", 160 | "async-sema": "^3.1.1", 161 | "bindings": "^1.4.0", 162 | "estree-walker": "2.0.2", 163 | "glob": "^7.1.3", 164 | "graceful-fs": "^4.2.9", 165 | "micromatch": "^4.0.2", 166 | "node-gyp-build": "^4.2.2", 167 | "resolve-from": "^5.0.0" 168 | }, 169 | "bin": { 170 | "nft": "out/cli.js" 171 | }, 172 | "engines": { 173 | "node": ">=16" 174 | } 175 | }, 176 | "node_modules/abbrev": { 177 | "version": "1.1.1", 178 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 179 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 180 | "dev": true 181 | }, 182 | "node_modules/acorn": { 183 | "version": "8.11.3", 184 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 185 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 186 | "dev": true, 187 | "bin": { 188 | "acorn": "bin/acorn" 189 | }, 190 | "engines": { 191 | "node": ">=0.4.0" 192 | } 193 | }, 194 | "node_modules/acorn-walk": { 195 | "version": "8.3.2", 196 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 197 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 198 | "dev": true, 199 | "engines": { 200 | "node": ">=0.4.0" 201 | } 202 | }, 203 | "node_modules/agent-base": { 204 | "version": "6.0.2", 205 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 206 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 207 | "dev": true, 208 | "dependencies": { 209 | "debug": "4" 210 | }, 211 | "engines": { 212 | "node": ">= 6.0.0" 213 | } 214 | }, 215 | "node_modules/ansi-regex": { 216 | "version": "6.0.1", 217 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 218 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 219 | "dev": true, 220 | "engines": { 221 | "node": ">=12" 222 | }, 223 | "funding": { 224 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 225 | } 226 | }, 227 | "node_modules/ansi-styles": { 228 | "version": "6.2.1", 229 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 230 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 231 | "dev": true, 232 | "engines": { 233 | "node": ">=12" 234 | }, 235 | "funding": { 236 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 237 | } 238 | }, 239 | "node_modules/aproba": { 240 | "version": "2.0.0", 241 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", 242 | "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", 243 | "dev": true 244 | }, 245 | "node_modules/are-we-there-yet": { 246 | "version": "2.0.0", 247 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 248 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 249 | "dev": true, 250 | "dependencies": { 251 | "delegates": "^1.0.0", 252 | "readable-stream": "^3.6.0" 253 | }, 254 | "engines": { 255 | "node": ">=10" 256 | } 257 | }, 258 | "node_modules/argparse": { 259 | "version": "1.0.10", 260 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 261 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 262 | "dev": true, 263 | "dependencies": { 264 | "sprintf-js": "~1.0.2" 265 | } 266 | }, 267 | "node_modules/array-find-index": { 268 | "version": "1.0.2", 269 | "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", 270 | "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", 271 | "dev": true, 272 | "engines": { 273 | "node": ">=0.10.0" 274 | } 275 | }, 276 | "node_modules/arrgv": { 277 | "version": "1.0.2", 278 | "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", 279 | "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", 280 | "dev": true, 281 | "engines": { 282 | "node": ">=8.0.0" 283 | } 284 | }, 285 | "node_modules/arrify": { 286 | "version": "3.0.0", 287 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", 288 | "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", 289 | "dev": true, 290 | "engines": { 291 | "node": ">=12" 292 | }, 293 | "funding": { 294 | "url": "https://github.com/sponsors/sindresorhus" 295 | } 296 | }, 297 | "node_modules/async-sema": { 298 | "version": "3.1.1", 299 | "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", 300 | "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", 301 | "dev": true 302 | }, 303 | "node_modules/ava": { 304 | "version": "6.0.1", 305 | "resolved": "https://registry.npmjs.org/ava/-/ava-6.0.1.tgz", 306 | "integrity": "sha512-9zR0wOwlcJdOWwHOKnpi0GrPRLTlxDFapGalP4rGD0oQRKxDVoucBBWvxVQ/2cPv10Hx1PkDXLJH5iUzhPn0/g==", 307 | "dev": true, 308 | "dependencies": { 309 | "@vercel/nft": "^0.24.4", 310 | "acorn": "^8.11.2", 311 | "acorn-walk": "^8.3.0", 312 | "ansi-styles": "^6.2.1", 313 | "arrgv": "^1.0.2", 314 | "arrify": "^3.0.0", 315 | "callsites": "^4.1.0", 316 | "cbor": "^9.0.1", 317 | "chalk": "^5.3.0", 318 | "chunkd": "^2.0.1", 319 | "ci-info": "^4.0.0", 320 | "ci-parallel-vars": "^1.0.1", 321 | "cli-truncate": "^4.0.0", 322 | "code-excerpt": "^4.0.0", 323 | "common-path-prefix": "^3.0.0", 324 | "concordance": "^5.0.4", 325 | "currently-unhandled": "^0.4.1", 326 | "debug": "^4.3.4", 327 | "emittery": "^1.0.1", 328 | "figures": "^6.0.1", 329 | "globby": "^14.0.0", 330 | "ignore-by-default": "^2.1.0", 331 | "indent-string": "^5.0.0", 332 | "is-plain-object": "^5.0.0", 333 | "is-promise": "^4.0.0", 334 | "matcher": "^5.0.0", 335 | "memoize": "^10.0.0", 336 | "ms": "^2.1.3", 337 | "p-map": "^6.0.0", 338 | "package-config": "^5.0.0", 339 | "picomatch": "^3.0.1", 340 | "plur": "^5.1.0", 341 | "pretty-ms": "^8.0.0", 342 | "resolve-cwd": "^3.0.0", 343 | "stack-utils": "^2.0.6", 344 | "strip-ansi": "^7.1.0", 345 | "supertap": "^3.0.1", 346 | "temp-dir": "^3.0.0", 347 | "write-file-atomic": "^5.0.1", 348 | "yargs": "^17.7.2" 349 | }, 350 | "bin": { 351 | "ava": "entrypoints/cli.mjs" 352 | }, 353 | "engines": { 354 | "node": "^18.18 || ^20.8 || ^21" 355 | }, 356 | "peerDependencies": { 357 | "@ava/typescript": "*" 358 | }, 359 | "peerDependenciesMeta": { 360 | "@ava/typescript": { 361 | "optional": true 362 | } 363 | } 364 | }, 365 | "node_modules/balanced-match": { 366 | "version": "1.0.2", 367 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 368 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 369 | "dev": true 370 | }, 371 | "node_modules/bindings": { 372 | "version": "1.5.0", 373 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 374 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 375 | "dev": true, 376 | "dependencies": { 377 | "file-uri-to-path": "1.0.0" 378 | } 379 | }, 380 | "node_modules/blueimp-md5": { 381 | "version": "2.19.0", 382 | "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", 383 | "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", 384 | "dev": true 385 | }, 386 | "node_modules/brace-expansion": { 387 | "version": "1.1.11", 388 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 389 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 390 | "dev": true, 391 | "dependencies": { 392 | "balanced-match": "^1.0.0", 393 | "concat-map": "0.0.1" 394 | } 395 | }, 396 | "node_modules/braces": { 397 | "version": "3.0.2", 398 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 399 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 400 | "dev": true, 401 | "dependencies": { 402 | "fill-range": "^7.0.1" 403 | }, 404 | "engines": { 405 | "node": ">=8" 406 | } 407 | }, 408 | "node_modules/browser-process-hrtime": { 409 | "version": "1.0.0", 410 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", 411 | "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", 412 | "dev": true 413 | }, 414 | "node_modules/callsites": { 415 | "version": "4.1.0", 416 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz", 417 | "integrity": "sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==", 418 | "dev": true, 419 | "engines": { 420 | "node": ">=12.20" 421 | }, 422 | "funding": { 423 | "url": "https://github.com/sponsors/sindresorhus" 424 | } 425 | }, 426 | "node_modules/cbor": { 427 | "version": "9.0.1", 428 | "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.1.tgz", 429 | "integrity": "sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ==", 430 | "dev": true, 431 | "dependencies": { 432 | "nofilter": "^3.1.0" 433 | }, 434 | "engines": { 435 | "node": ">=16" 436 | } 437 | }, 438 | "node_modules/chalk": { 439 | "version": "5.3.0", 440 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", 441 | "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", 442 | "dev": true, 443 | "engines": { 444 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 445 | }, 446 | "funding": { 447 | "url": "https://github.com/chalk/chalk?sponsor=1" 448 | } 449 | }, 450 | "node_modules/chownr": { 451 | "version": "2.0.0", 452 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 453 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", 454 | "dev": true, 455 | "engines": { 456 | "node": ">=10" 457 | } 458 | }, 459 | "node_modules/chunkd": { 460 | "version": "2.0.1", 461 | "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", 462 | "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", 463 | "dev": true 464 | }, 465 | "node_modules/ci-info": { 466 | "version": "4.0.0", 467 | "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", 468 | "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", 469 | "dev": true, 470 | "funding": [ 471 | { 472 | "type": "github", 473 | "url": "https://github.com/sponsors/sibiraj-s" 474 | } 475 | ], 476 | "engines": { 477 | "node": ">=8" 478 | } 479 | }, 480 | "node_modules/ci-parallel-vars": { 481 | "version": "1.0.1", 482 | "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", 483 | "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", 484 | "dev": true 485 | }, 486 | "node_modules/cli-truncate": { 487 | "version": "4.0.0", 488 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", 489 | "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", 490 | "dev": true, 491 | "dependencies": { 492 | "slice-ansi": "^5.0.0", 493 | "string-width": "^7.0.0" 494 | }, 495 | "engines": { 496 | "node": ">=18" 497 | }, 498 | "funding": { 499 | "url": "https://github.com/sponsors/sindresorhus" 500 | } 501 | }, 502 | "node_modules/cliui": { 503 | "version": "8.0.1", 504 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 505 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 506 | "dev": true, 507 | "dependencies": { 508 | "string-width": "^4.2.0", 509 | "strip-ansi": "^6.0.1", 510 | "wrap-ansi": "^7.0.0" 511 | }, 512 | "engines": { 513 | "node": ">=12" 514 | } 515 | }, 516 | "node_modules/cliui/node_modules/ansi-regex": { 517 | "version": "5.0.1", 518 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 519 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 520 | "dev": true, 521 | "engines": { 522 | "node": ">=8" 523 | } 524 | }, 525 | "node_modules/cliui/node_modules/emoji-regex": { 526 | "version": "8.0.0", 527 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 528 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 529 | "dev": true 530 | }, 531 | "node_modules/cliui/node_modules/is-fullwidth-code-point": { 532 | "version": "3.0.0", 533 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 534 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 535 | "dev": true, 536 | "engines": { 537 | "node": ">=8" 538 | } 539 | }, 540 | "node_modules/cliui/node_modules/string-width": { 541 | "version": "4.2.3", 542 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 543 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 544 | "dev": true, 545 | "dependencies": { 546 | "emoji-regex": "^8.0.0", 547 | "is-fullwidth-code-point": "^3.0.0", 548 | "strip-ansi": "^6.0.1" 549 | }, 550 | "engines": { 551 | "node": ">=8" 552 | } 553 | }, 554 | "node_modules/cliui/node_modules/strip-ansi": { 555 | "version": "6.0.1", 556 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 557 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 558 | "dev": true, 559 | "dependencies": { 560 | "ansi-regex": "^5.0.1" 561 | }, 562 | "engines": { 563 | "node": ">=8" 564 | } 565 | }, 566 | "node_modules/code-excerpt": { 567 | "version": "4.0.0", 568 | "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", 569 | "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", 570 | "dev": true, 571 | "dependencies": { 572 | "convert-to-spaces": "^2.0.1" 573 | }, 574 | "engines": { 575 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 576 | } 577 | }, 578 | "node_modules/color-convert": { 579 | "version": "2.0.1", 580 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 581 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 582 | "dev": true, 583 | "dependencies": { 584 | "color-name": "~1.1.4" 585 | }, 586 | "engines": { 587 | "node": ">=7.0.0" 588 | } 589 | }, 590 | "node_modules/color-name": { 591 | "version": "1.1.4", 592 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 593 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 594 | "dev": true 595 | }, 596 | "node_modules/color-support": { 597 | "version": "1.1.3", 598 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 599 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", 600 | "dev": true, 601 | "bin": { 602 | "color-support": "bin.js" 603 | } 604 | }, 605 | "node_modules/common-path-prefix": { 606 | "version": "3.0.0", 607 | "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", 608 | "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", 609 | "dev": true 610 | }, 611 | "node_modules/concat-map": { 612 | "version": "0.0.1", 613 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 614 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 615 | "dev": true 616 | }, 617 | "node_modules/concordance": { 618 | "version": "5.0.4", 619 | "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", 620 | "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", 621 | "dev": true, 622 | "dependencies": { 623 | "date-time": "^3.1.0", 624 | "esutils": "^2.0.3", 625 | "fast-diff": "^1.2.0", 626 | "js-string-escape": "^1.0.1", 627 | "lodash": "^4.17.15", 628 | "md5-hex": "^3.0.1", 629 | "semver": "^7.3.2", 630 | "well-known-symbols": "^2.0.0" 631 | }, 632 | "engines": { 633 | "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" 634 | } 635 | }, 636 | "node_modules/console-control-strings": { 637 | "version": "1.1.0", 638 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 639 | "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", 640 | "dev": true 641 | }, 642 | "node_modules/convert-to-spaces": { 643 | "version": "2.0.1", 644 | "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", 645 | "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", 646 | "dev": true, 647 | "engines": { 648 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 649 | } 650 | }, 651 | "node_modules/cross-spawn": { 652 | "version": "7.0.3", 653 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 654 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 655 | "dev": true, 656 | "dependencies": { 657 | "path-key": "^3.1.0", 658 | "shebang-command": "^2.0.0", 659 | "which": "^2.0.1" 660 | }, 661 | "engines": { 662 | "node": ">= 8" 663 | } 664 | }, 665 | "node_modules/currently-unhandled": { 666 | "version": "0.4.1", 667 | "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", 668 | "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", 669 | "dev": true, 670 | "dependencies": { 671 | "array-find-index": "^1.0.1" 672 | }, 673 | "engines": { 674 | "node": ">=0.10.0" 675 | } 676 | }, 677 | "node_modules/date-time": { 678 | "version": "3.1.0", 679 | "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", 680 | "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", 681 | "dev": true, 682 | "dependencies": { 683 | "time-zone": "^1.0.0" 684 | }, 685 | "engines": { 686 | "node": ">=6" 687 | } 688 | }, 689 | "node_modules/debug": { 690 | "version": "4.3.4", 691 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 692 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 693 | "dev": true, 694 | "dependencies": { 695 | "ms": "2.1.2" 696 | }, 697 | "engines": { 698 | "node": ">=6.0" 699 | }, 700 | "peerDependenciesMeta": { 701 | "supports-color": { 702 | "optional": true 703 | } 704 | } 705 | }, 706 | "node_modules/debug/node_modules/ms": { 707 | "version": "2.1.2", 708 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 709 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 710 | "dev": true 711 | }, 712 | "node_modules/delegates": { 713 | "version": "1.0.0", 714 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 715 | "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", 716 | "dev": true 717 | }, 718 | "node_modules/detect-libc": { 719 | "version": "2.0.2", 720 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", 721 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", 722 | "dev": true, 723 | "engines": { 724 | "node": ">=8" 725 | } 726 | }, 727 | "node_modules/dom-urls": { 728 | "version": "1.1.0", 729 | "resolved": "https://registry.npmjs.org/dom-urls/-/dom-urls-1.1.0.tgz", 730 | "integrity": "sha512-LNxCeExaNbczqMVfQUyLdd+r+smG7ixIa+doeyiJ7nTmL8aZRrJhHkEYBEYVGvYv7k2DOEBh2eKthoCmWpfICg==", 731 | "dev": true, 732 | "dependencies": { 733 | "urijs": "^1.16.1" 734 | }, 735 | "engines": { 736 | "node": ">=0.8.0" 737 | } 738 | }, 739 | "node_modules/emittery": { 740 | "version": "1.0.1", 741 | "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.0.1.tgz", 742 | "integrity": "sha512-2ID6FdrMD9KDLldGesP6317G78K7km/kMcwItRtVFva7I/cSEOIaLpewaUb+YLXVwdAp3Ctfxh/V5zIl1sj7dQ==", 743 | "dev": true, 744 | "engines": { 745 | "node": ">=14.16" 746 | }, 747 | "funding": { 748 | "url": "https://github.com/sindresorhus/emittery?sponsor=1" 749 | } 750 | }, 751 | "node_modules/emoji-regex": { 752 | "version": "10.3.0", 753 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", 754 | "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", 755 | "dev": true 756 | }, 757 | "node_modules/escalade": { 758 | "version": "3.1.1", 759 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 760 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 761 | "dev": true, 762 | "engines": { 763 | "node": ">=6" 764 | } 765 | }, 766 | "node_modules/escape-string-regexp": { 767 | "version": "5.0.0", 768 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", 769 | "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", 770 | "dev": true, 771 | "engines": { 772 | "node": ">=12" 773 | }, 774 | "funding": { 775 | "url": "https://github.com/sponsors/sindresorhus" 776 | } 777 | }, 778 | "node_modules/esprima": { 779 | "version": "4.0.1", 780 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 781 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 782 | "dev": true, 783 | "bin": { 784 | "esparse": "bin/esparse.js", 785 | "esvalidate": "bin/esvalidate.js" 786 | }, 787 | "engines": { 788 | "node": ">=4" 789 | } 790 | }, 791 | "node_modules/estree-walker": { 792 | "version": "2.0.2", 793 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 794 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 795 | "dev": true 796 | }, 797 | "node_modules/esutils": { 798 | "version": "2.0.3", 799 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 800 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 801 | "dev": true, 802 | "engines": { 803 | "node": ">=0.10.0" 804 | } 805 | }, 806 | "node_modules/execa": { 807 | "version": "7.2.0", 808 | "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", 809 | "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", 810 | "dev": true, 811 | "dependencies": { 812 | "cross-spawn": "^7.0.3", 813 | "get-stream": "^6.0.1", 814 | "human-signals": "^4.3.0", 815 | "is-stream": "^3.0.0", 816 | "merge-stream": "^2.0.0", 817 | "npm-run-path": "^5.1.0", 818 | "onetime": "^6.0.0", 819 | "signal-exit": "^3.0.7", 820 | "strip-final-newline": "^3.0.0" 821 | }, 822 | "engines": { 823 | "node": "^14.18.0 || ^16.14.0 || >=18.0.0" 824 | }, 825 | "funding": { 826 | "url": "https://github.com/sindresorhus/execa?sponsor=1" 827 | } 828 | }, 829 | "node_modules/fast-diff": { 830 | "version": "1.3.0", 831 | "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", 832 | "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", 833 | "dev": true 834 | }, 835 | "node_modules/fast-glob": { 836 | "version": "3.3.2", 837 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 838 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 839 | "dev": true, 840 | "dependencies": { 841 | "@nodelib/fs.stat": "^2.0.2", 842 | "@nodelib/fs.walk": "^1.2.3", 843 | "glob-parent": "^5.1.2", 844 | "merge2": "^1.3.0", 845 | "micromatch": "^4.0.4" 846 | }, 847 | "engines": { 848 | "node": ">=8.6.0" 849 | } 850 | }, 851 | "node_modules/fastq": { 852 | "version": "1.16.0", 853 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", 854 | "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", 855 | "dev": true, 856 | "dependencies": { 857 | "reusify": "^1.0.4" 858 | } 859 | }, 860 | "node_modules/figures": { 861 | "version": "6.0.1", 862 | "resolved": "https://registry.npmjs.org/figures/-/figures-6.0.1.tgz", 863 | "integrity": "sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==", 864 | "dev": true, 865 | "dependencies": { 866 | "is-unicode-supported": "^2.0.0" 867 | }, 868 | "engines": { 869 | "node": ">=18" 870 | }, 871 | "funding": { 872 | "url": "https://github.com/sponsors/sindresorhus" 873 | } 874 | }, 875 | "node_modules/file-uri-to-path": { 876 | "version": "1.0.0", 877 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 878 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 879 | "dev": true 880 | }, 881 | "node_modules/fill-range": { 882 | "version": "7.0.1", 883 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 884 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 885 | "dev": true, 886 | "dependencies": { 887 | "to-regex-range": "^5.0.1" 888 | }, 889 | "engines": { 890 | "node": ">=8" 891 | } 892 | }, 893 | "node_modules/find-up-simple": { 894 | "version": "1.0.0", 895 | "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", 896 | "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", 897 | "dev": true, 898 | "engines": { 899 | "node": ">=18" 900 | }, 901 | "funding": { 902 | "url": "https://github.com/sponsors/sindresorhus" 903 | } 904 | }, 905 | "node_modules/fs-minipass": { 906 | "version": "2.1.0", 907 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 908 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 909 | "dev": true, 910 | "dependencies": { 911 | "minipass": "^3.0.0" 912 | }, 913 | "engines": { 914 | "node": ">= 8" 915 | } 916 | }, 917 | "node_modules/fs-minipass/node_modules/minipass": { 918 | "version": "3.3.6", 919 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 920 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 921 | "dev": true, 922 | "dependencies": { 923 | "yallist": "^4.0.0" 924 | }, 925 | "engines": { 926 | "node": ">=8" 927 | } 928 | }, 929 | "node_modules/fs.realpath": { 930 | "version": "1.0.0", 931 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 932 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 933 | "dev": true 934 | }, 935 | "node_modules/gauge": { 936 | "version": "3.0.2", 937 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 938 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 939 | "dev": true, 940 | "dependencies": { 941 | "aproba": "^1.0.3 || ^2.0.0", 942 | "color-support": "^1.1.2", 943 | "console-control-strings": "^1.0.0", 944 | "has-unicode": "^2.0.1", 945 | "object-assign": "^4.1.1", 946 | "signal-exit": "^3.0.0", 947 | "string-width": "^4.2.3", 948 | "strip-ansi": "^6.0.1", 949 | "wide-align": "^1.1.2" 950 | }, 951 | "engines": { 952 | "node": ">=10" 953 | } 954 | }, 955 | "node_modules/gauge/node_modules/ansi-regex": { 956 | "version": "5.0.1", 957 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 958 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 959 | "dev": true, 960 | "engines": { 961 | "node": ">=8" 962 | } 963 | }, 964 | "node_modules/gauge/node_modules/emoji-regex": { 965 | "version": "8.0.0", 966 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 967 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 968 | "dev": true 969 | }, 970 | "node_modules/gauge/node_modules/is-fullwidth-code-point": { 971 | "version": "3.0.0", 972 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 973 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 974 | "dev": true, 975 | "engines": { 976 | "node": ">=8" 977 | } 978 | }, 979 | "node_modules/gauge/node_modules/string-width": { 980 | "version": "4.2.3", 981 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 982 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 983 | "dev": true, 984 | "dependencies": { 985 | "emoji-regex": "^8.0.0", 986 | "is-fullwidth-code-point": "^3.0.0", 987 | "strip-ansi": "^6.0.1" 988 | }, 989 | "engines": { 990 | "node": ">=8" 991 | } 992 | }, 993 | "node_modules/gauge/node_modules/strip-ansi": { 994 | "version": "6.0.1", 995 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 996 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 997 | "dev": true, 998 | "dependencies": { 999 | "ansi-regex": "^5.0.1" 1000 | }, 1001 | "engines": { 1002 | "node": ">=8" 1003 | } 1004 | }, 1005 | "node_modules/get-caller-file": { 1006 | "version": "2.0.5", 1007 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 1008 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 1009 | "dev": true, 1010 | "engines": { 1011 | "node": "6.* || 8.* || >= 10.*" 1012 | } 1013 | }, 1014 | "node_modules/get-east-asian-width": { 1015 | "version": "1.2.0", 1016 | "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", 1017 | "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", 1018 | "dev": true, 1019 | "engines": { 1020 | "node": ">=18" 1021 | }, 1022 | "funding": { 1023 | "url": "https://github.com/sponsors/sindresorhus" 1024 | } 1025 | }, 1026 | "node_modules/get-stream": { 1027 | "version": "6.0.1", 1028 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", 1029 | "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", 1030 | "dev": true, 1031 | "engines": { 1032 | "node": ">=10" 1033 | }, 1034 | "funding": { 1035 | "url": "https://github.com/sponsors/sindresorhus" 1036 | } 1037 | }, 1038 | "node_modules/glob": { 1039 | "version": "7.2.3", 1040 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1041 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1042 | "dev": true, 1043 | "dependencies": { 1044 | "fs.realpath": "^1.0.0", 1045 | "inflight": "^1.0.4", 1046 | "inherits": "2", 1047 | "minimatch": "^3.1.1", 1048 | "once": "^1.3.0", 1049 | "path-is-absolute": "^1.0.0" 1050 | }, 1051 | "engines": { 1052 | "node": "*" 1053 | }, 1054 | "funding": { 1055 | "url": "https://github.com/sponsors/isaacs" 1056 | } 1057 | }, 1058 | "node_modules/glob-parent": { 1059 | "version": "5.1.2", 1060 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1061 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1062 | "dev": true, 1063 | "dependencies": { 1064 | "is-glob": "^4.0.1" 1065 | }, 1066 | "engines": { 1067 | "node": ">= 6" 1068 | } 1069 | }, 1070 | "node_modules/globby": { 1071 | "version": "14.0.0", 1072 | "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", 1073 | "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", 1074 | "dev": true, 1075 | "dependencies": { 1076 | "@sindresorhus/merge-streams": "^1.0.0", 1077 | "fast-glob": "^3.3.2", 1078 | "ignore": "^5.2.4", 1079 | "path-type": "^5.0.0", 1080 | "slash": "^5.1.0", 1081 | "unicorn-magic": "^0.1.0" 1082 | }, 1083 | "engines": { 1084 | "node": ">=18" 1085 | }, 1086 | "funding": { 1087 | "url": "https://github.com/sponsors/sindresorhus" 1088 | } 1089 | }, 1090 | "node_modules/graceful-fs": { 1091 | "version": "4.2.11", 1092 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1093 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1094 | "dev": true 1095 | }, 1096 | "node_modules/has-unicode": { 1097 | "version": "2.0.1", 1098 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 1099 | "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", 1100 | "dev": true 1101 | }, 1102 | "node_modules/https-proxy-agent": { 1103 | "version": "5.0.1", 1104 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 1105 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 1106 | "dev": true, 1107 | "dependencies": { 1108 | "agent-base": "6", 1109 | "debug": "4" 1110 | }, 1111 | "engines": { 1112 | "node": ">= 6" 1113 | } 1114 | }, 1115 | "node_modules/human-signals": { 1116 | "version": "4.3.1", 1117 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", 1118 | "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", 1119 | "dev": true, 1120 | "engines": { 1121 | "node": ">=14.18.0" 1122 | } 1123 | }, 1124 | "node_modules/ignore": { 1125 | "version": "5.3.0", 1126 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", 1127 | "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", 1128 | "dev": true, 1129 | "engines": { 1130 | "node": ">= 4" 1131 | } 1132 | }, 1133 | "node_modules/ignore-by-default": { 1134 | "version": "2.1.0", 1135 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", 1136 | "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", 1137 | "dev": true, 1138 | "engines": { 1139 | "node": ">=10 <11 || >=12 <13 || >=14" 1140 | } 1141 | }, 1142 | "node_modules/imurmurhash": { 1143 | "version": "0.1.4", 1144 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1145 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1146 | "dev": true, 1147 | "engines": { 1148 | "node": ">=0.8.19" 1149 | } 1150 | }, 1151 | "node_modules/indent-string": { 1152 | "version": "5.0.0", 1153 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", 1154 | "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", 1155 | "dev": true, 1156 | "engines": { 1157 | "node": ">=12" 1158 | }, 1159 | "funding": { 1160 | "url": "https://github.com/sponsors/sindresorhus" 1161 | } 1162 | }, 1163 | "node_modules/inflight": { 1164 | "version": "1.0.6", 1165 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1166 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1167 | "dev": true, 1168 | "dependencies": { 1169 | "once": "^1.3.0", 1170 | "wrappy": "1" 1171 | } 1172 | }, 1173 | "node_modules/inherits": { 1174 | "version": "2.0.4", 1175 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1176 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1177 | "dev": true 1178 | }, 1179 | "node_modules/irregular-plurals": { 1180 | "version": "3.5.0", 1181 | "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", 1182 | "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", 1183 | "dev": true, 1184 | "engines": { 1185 | "node": ">=8" 1186 | } 1187 | }, 1188 | "node_modules/is-extglob": { 1189 | "version": "2.1.1", 1190 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1191 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1192 | "dev": true, 1193 | "engines": { 1194 | "node": ">=0.10.0" 1195 | } 1196 | }, 1197 | "node_modules/is-fullwidth-code-point": { 1198 | "version": "4.0.0", 1199 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", 1200 | "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", 1201 | "dev": true, 1202 | "engines": { 1203 | "node": ">=12" 1204 | }, 1205 | "funding": { 1206 | "url": "https://github.com/sponsors/sindresorhus" 1207 | } 1208 | }, 1209 | "node_modules/is-glob": { 1210 | "version": "4.0.3", 1211 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1212 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1213 | "dev": true, 1214 | "dependencies": { 1215 | "is-extglob": "^2.1.1" 1216 | }, 1217 | "engines": { 1218 | "node": ">=0.10.0" 1219 | } 1220 | }, 1221 | "node_modules/is-number": { 1222 | "version": "7.0.0", 1223 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1224 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1225 | "dev": true, 1226 | "engines": { 1227 | "node": ">=0.12.0" 1228 | } 1229 | }, 1230 | "node_modules/is-plain-object": { 1231 | "version": "5.0.0", 1232 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", 1233 | "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", 1234 | "dev": true, 1235 | "engines": { 1236 | "node": ">=0.10.0" 1237 | } 1238 | }, 1239 | "node_modules/is-promise": { 1240 | "version": "4.0.0", 1241 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 1242 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 1243 | "dev": true 1244 | }, 1245 | "node_modules/is-stream": { 1246 | "version": "3.0.0", 1247 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", 1248 | "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", 1249 | "dev": true, 1250 | "engines": { 1251 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1252 | }, 1253 | "funding": { 1254 | "url": "https://github.com/sponsors/sindresorhus" 1255 | } 1256 | }, 1257 | "node_modules/is-unicode-supported": { 1258 | "version": "2.0.0", 1259 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", 1260 | "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", 1261 | "dev": true, 1262 | "engines": { 1263 | "node": ">=18" 1264 | }, 1265 | "funding": { 1266 | "url": "https://github.com/sponsors/sindresorhus" 1267 | } 1268 | }, 1269 | "node_modules/isexe": { 1270 | "version": "2.0.0", 1271 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1272 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1273 | "dev": true 1274 | }, 1275 | "node_modules/js-string-escape": { 1276 | "version": "1.0.1", 1277 | "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", 1278 | "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", 1279 | "dev": true, 1280 | "engines": { 1281 | "node": ">= 0.8" 1282 | } 1283 | }, 1284 | "node_modules/js-yaml": { 1285 | "version": "3.14.1", 1286 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 1287 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 1288 | "dev": true, 1289 | "dependencies": { 1290 | "argparse": "^1.0.7", 1291 | "esprima": "^4.0.0" 1292 | }, 1293 | "bin": { 1294 | "js-yaml": "bin/js-yaml.js" 1295 | } 1296 | }, 1297 | "node_modules/load-json-file": { 1298 | "version": "7.0.1", 1299 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", 1300 | "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", 1301 | "dev": true, 1302 | "engines": { 1303 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1304 | }, 1305 | "funding": { 1306 | "url": "https://github.com/sponsors/sindresorhus" 1307 | } 1308 | }, 1309 | "node_modules/lodash": { 1310 | "version": "4.17.21", 1311 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1312 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 1313 | "dev": true 1314 | }, 1315 | "node_modules/lodash._basefor": { 1316 | "version": "3.0.3", 1317 | "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", 1318 | "integrity": "sha512-6bc3b8grkpMgDcVJv9JYZAk/mHgcqMljzm7OsbmcE2FGUMmmLQTPHlh/dFqR8LA0GQ7z4K67JSotVKu5058v1A==", 1319 | "dev": true 1320 | }, 1321 | "node_modules/lodash.isarguments": { 1322 | "version": "3.1.0", 1323 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 1324 | "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", 1325 | "dev": true 1326 | }, 1327 | "node_modules/lodash.isarray": { 1328 | "version": "3.0.4", 1329 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 1330 | "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", 1331 | "dev": true 1332 | }, 1333 | "node_modules/lodash.isplainobject": { 1334 | "version": "3.2.0", 1335 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", 1336 | "integrity": "sha512-P4wZnho5curNqeEq/x292Pb57e1v+woR7DJ84DURelKB46lby8aDEGVobSaYtzHdQBWQrJSdxcCwjlGOvvdIyg==", 1337 | "dev": true, 1338 | "dependencies": { 1339 | "lodash._basefor": "^3.0.0", 1340 | "lodash.isarguments": "^3.0.0", 1341 | "lodash.keysin": "^3.0.0" 1342 | } 1343 | }, 1344 | "node_modules/lodash.keysin": { 1345 | "version": "3.0.8", 1346 | "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz", 1347 | "integrity": "sha512-YDB/5xkL3fBKFMDaC+cfGV00pbiJ6XoJIfRmBhv7aR6wWtbCW6IzkiWnTfkiHTF6ALD7ff83dAtB3OEaSoyQPg==", 1348 | "dev": true, 1349 | "dependencies": { 1350 | "lodash.isarguments": "^3.0.0", 1351 | "lodash.isarray": "^3.0.0" 1352 | } 1353 | }, 1354 | "node_modules/lru-cache": { 1355 | "version": "6.0.0", 1356 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1357 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1358 | "dev": true, 1359 | "dependencies": { 1360 | "yallist": "^4.0.0" 1361 | }, 1362 | "engines": { 1363 | "node": ">=10" 1364 | } 1365 | }, 1366 | "node_modules/make-dir": { 1367 | "version": "3.1.0", 1368 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 1369 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 1370 | "dev": true, 1371 | "dependencies": { 1372 | "semver": "^6.0.0" 1373 | }, 1374 | "engines": { 1375 | "node": ">=8" 1376 | }, 1377 | "funding": { 1378 | "url": "https://github.com/sponsors/sindresorhus" 1379 | } 1380 | }, 1381 | "node_modules/make-dir/node_modules/semver": { 1382 | "version": "6.3.1", 1383 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 1384 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 1385 | "dev": true, 1386 | "bin": { 1387 | "semver": "bin/semver.js" 1388 | } 1389 | }, 1390 | "node_modules/matcher": { 1391 | "version": "5.0.0", 1392 | "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", 1393 | "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", 1394 | "dev": true, 1395 | "dependencies": { 1396 | "escape-string-regexp": "^5.0.0" 1397 | }, 1398 | "engines": { 1399 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1400 | }, 1401 | "funding": { 1402 | "url": "https://github.com/sponsors/sindresorhus" 1403 | } 1404 | }, 1405 | "node_modules/md5-hex": { 1406 | "version": "3.0.1", 1407 | "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", 1408 | "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", 1409 | "dev": true, 1410 | "dependencies": { 1411 | "blueimp-md5": "^2.10.0" 1412 | }, 1413 | "engines": { 1414 | "node": ">=8" 1415 | } 1416 | }, 1417 | "node_modules/memoize": { 1418 | "version": "10.0.0", 1419 | "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz", 1420 | "integrity": "sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==", 1421 | "dev": true, 1422 | "dependencies": { 1423 | "mimic-function": "^5.0.0" 1424 | }, 1425 | "engines": { 1426 | "node": ">=18" 1427 | }, 1428 | "funding": { 1429 | "url": "https://github.com/sindresorhus/memoize?sponsor=1" 1430 | } 1431 | }, 1432 | "node_modules/merge-stream": { 1433 | "version": "2.0.0", 1434 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 1435 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 1436 | "dev": true 1437 | }, 1438 | "node_modules/merge2": { 1439 | "version": "1.4.1", 1440 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1441 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1442 | "dev": true, 1443 | "engines": { 1444 | "node": ">= 8" 1445 | } 1446 | }, 1447 | "node_modules/micromatch": { 1448 | "version": "4.0.5", 1449 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 1450 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 1451 | "dev": true, 1452 | "dependencies": { 1453 | "braces": "^3.0.2", 1454 | "picomatch": "^2.3.1" 1455 | }, 1456 | "engines": { 1457 | "node": ">=8.6" 1458 | } 1459 | }, 1460 | "node_modules/micromatch/node_modules/picomatch": { 1461 | "version": "2.3.1", 1462 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1463 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1464 | "dev": true, 1465 | "engines": { 1466 | "node": ">=8.6" 1467 | }, 1468 | "funding": { 1469 | "url": "https://github.com/sponsors/jonschlinkert" 1470 | } 1471 | }, 1472 | "node_modules/mime": { 1473 | "version": "3.0.0", 1474 | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 1475 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 1476 | "bin": { 1477 | "mime": "cli.js" 1478 | }, 1479 | "engines": { 1480 | "node": ">=10.0.0" 1481 | } 1482 | }, 1483 | "node_modules/mimic-fn": { 1484 | "version": "4.0.0", 1485 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", 1486 | "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", 1487 | "dev": true, 1488 | "engines": { 1489 | "node": ">=12" 1490 | }, 1491 | "funding": { 1492 | "url": "https://github.com/sponsors/sindresorhus" 1493 | } 1494 | }, 1495 | "node_modules/mimic-function": { 1496 | "version": "5.0.0", 1497 | "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.0.tgz", 1498 | "integrity": "sha512-RBfQ+9X9DpXdEoK7Bu+KeEU6vFhumEIiXKWECPzRBmDserEq4uR2b/VCm0LwpMSosoq2k+Zuxj/GzOr0Fn6h/g==", 1499 | "dev": true, 1500 | "engines": { 1501 | "node": ">=18" 1502 | }, 1503 | "funding": { 1504 | "url": "https://github.com/sponsors/sindresorhus" 1505 | } 1506 | }, 1507 | "node_modules/minimatch": { 1508 | "version": "3.1.2", 1509 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1510 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1511 | "dev": true, 1512 | "dependencies": { 1513 | "brace-expansion": "^1.1.7" 1514 | }, 1515 | "engines": { 1516 | "node": "*" 1517 | } 1518 | }, 1519 | "node_modules/minipass": { 1520 | "version": "5.0.0", 1521 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", 1522 | "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", 1523 | "dev": true, 1524 | "engines": { 1525 | "node": ">=8" 1526 | } 1527 | }, 1528 | "node_modules/minizlib": { 1529 | "version": "2.1.2", 1530 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 1531 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 1532 | "dev": true, 1533 | "dependencies": { 1534 | "minipass": "^3.0.0", 1535 | "yallist": "^4.0.0" 1536 | }, 1537 | "engines": { 1538 | "node": ">= 8" 1539 | } 1540 | }, 1541 | "node_modules/minizlib/node_modules/minipass": { 1542 | "version": "3.3.6", 1543 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 1544 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 1545 | "dev": true, 1546 | "dependencies": { 1547 | "yallist": "^4.0.0" 1548 | }, 1549 | "engines": { 1550 | "node": ">=8" 1551 | } 1552 | }, 1553 | "node_modules/mkdirp": { 1554 | "version": "1.0.4", 1555 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 1556 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 1557 | "dev": true, 1558 | "bin": { 1559 | "mkdirp": "bin/cmd.js" 1560 | }, 1561 | "engines": { 1562 | "node": ">=10" 1563 | } 1564 | }, 1565 | "node_modules/ms": { 1566 | "version": "2.1.3", 1567 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1568 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1569 | "dev": true 1570 | }, 1571 | "node_modules/node-fetch": { 1572 | "version": "2.7.0", 1573 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 1574 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 1575 | "dev": true, 1576 | "dependencies": { 1577 | "whatwg-url": "^5.0.0" 1578 | }, 1579 | "engines": { 1580 | "node": "4.x || >=6.0.0" 1581 | }, 1582 | "peerDependencies": { 1583 | "encoding": "^0.1.0" 1584 | }, 1585 | "peerDependenciesMeta": { 1586 | "encoding": { 1587 | "optional": true 1588 | } 1589 | } 1590 | }, 1591 | "node_modules/node-gyp-build": { 1592 | "version": "4.8.0", 1593 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", 1594 | "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", 1595 | "dev": true, 1596 | "bin": { 1597 | "node-gyp-build": "bin.js", 1598 | "node-gyp-build-optional": "optional.js", 1599 | "node-gyp-build-test": "build-test.js" 1600 | } 1601 | }, 1602 | "node_modules/nofilter": { 1603 | "version": "3.1.0", 1604 | "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", 1605 | "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", 1606 | "dev": true, 1607 | "engines": { 1608 | "node": ">=12.19" 1609 | } 1610 | }, 1611 | "node_modules/nopt": { 1612 | "version": "5.0.0", 1613 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 1614 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 1615 | "dev": true, 1616 | "dependencies": { 1617 | "abbrev": "1" 1618 | }, 1619 | "bin": { 1620 | "nopt": "bin/nopt.js" 1621 | }, 1622 | "engines": { 1623 | "node": ">=6" 1624 | } 1625 | }, 1626 | "node_modules/npm-run-path": { 1627 | "version": "5.2.0", 1628 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", 1629 | "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", 1630 | "dev": true, 1631 | "dependencies": { 1632 | "path-key": "^4.0.0" 1633 | }, 1634 | "engines": { 1635 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1636 | }, 1637 | "funding": { 1638 | "url": "https://github.com/sponsors/sindresorhus" 1639 | } 1640 | }, 1641 | "node_modules/npm-run-path/node_modules/path-key": { 1642 | "version": "4.0.0", 1643 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", 1644 | "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", 1645 | "dev": true, 1646 | "engines": { 1647 | "node": ">=12" 1648 | }, 1649 | "funding": { 1650 | "url": "https://github.com/sponsors/sindresorhus" 1651 | } 1652 | }, 1653 | "node_modules/npmlog": { 1654 | "version": "5.0.1", 1655 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 1656 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 1657 | "dev": true, 1658 | "dependencies": { 1659 | "are-we-there-yet": "^2.0.0", 1660 | "console-control-strings": "^1.1.0", 1661 | "gauge": "^3.0.0", 1662 | "set-blocking": "^2.0.0" 1663 | } 1664 | }, 1665 | "node_modules/object-assign": { 1666 | "version": "4.1.1", 1667 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1668 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1669 | "dev": true, 1670 | "engines": { 1671 | "node": ">=0.10.0" 1672 | } 1673 | }, 1674 | "node_modules/once": { 1675 | "version": "1.4.0", 1676 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1677 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1678 | "dev": true, 1679 | "dependencies": { 1680 | "wrappy": "1" 1681 | } 1682 | }, 1683 | "node_modules/onetime": { 1684 | "version": "6.0.0", 1685 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", 1686 | "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", 1687 | "dev": true, 1688 | "dependencies": { 1689 | "mimic-fn": "^4.0.0" 1690 | }, 1691 | "engines": { 1692 | "node": ">=12" 1693 | }, 1694 | "funding": { 1695 | "url": "https://github.com/sponsors/sindresorhus" 1696 | } 1697 | }, 1698 | "node_modules/p-map": { 1699 | "version": "6.0.0", 1700 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-6.0.0.tgz", 1701 | "integrity": "sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==", 1702 | "dev": true, 1703 | "engines": { 1704 | "node": ">=16" 1705 | }, 1706 | "funding": { 1707 | "url": "https://github.com/sponsors/sindresorhus" 1708 | } 1709 | }, 1710 | "node_modules/package-config": { 1711 | "version": "5.0.0", 1712 | "resolved": "https://registry.npmjs.org/package-config/-/package-config-5.0.0.tgz", 1713 | "integrity": "sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==", 1714 | "dev": true, 1715 | "dependencies": { 1716 | "find-up-simple": "^1.0.0", 1717 | "load-json-file": "^7.0.1" 1718 | }, 1719 | "engines": { 1720 | "node": ">=18" 1721 | }, 1722 | "funding": { 1723 | "url": "https://github.com/sponsors/sindresorhus" 1724 | } 1725 | }, 1726 | "node_modules/parse-ms": { 1727 | "version": "3.0.0", 1728 | "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", 1729 | "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", 1730 | "dev": true, 1731 | "engines": { 1732 | "node": ">=12" 1733 | }, 1734 | "funding": { 1735 | "url": "https://github.com/sponsors/sindresorhus" 1736 | } 1737 | }, 1738 | "node_modules/path-is-absolute": { 1739 | "version": "1.0.1", 1740 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1741 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1742 | "dev": true, 1743 | "engines": { 1744 | "node": ">=0.10.0" 1745 | } 1746 | }, 1747 | "node_modules/path-key": { 1748 | "version": "3.1.1", 1749 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1750 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1751 | "dev": true, 1752 | "engines": { 1753 | "node": ">=8" 1754 | } 1755 | }, 1756 | "node_modules/path-type": { 1757 | "version": "5.0.0", 1758 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", 1759 | "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", 1760 | "dev": true, 1761 | "engines": { 1762 | "node": ">=12" 1763 | }, 1764 | "funding": { 1765 | "url": "https://github.com/sponsors/sindresorhus" 1766 | } 1767 | }, 1768 | "node_modules/picomatch": { 1769 | "version": "3.0.1", 1770 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", 1771 | "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", 1772 | "dev": true, 1773 | "engines": { 1774 | "node": ">=10" 1775 | }, 1776 | "funding": { 1777 | "url": "https://github.com/sponsors/jonschlinkert" 1778 | } 1779 | }, 1780 | "node_modules/plur": { 1781 | "version": "5.1.0", 1782 | "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", 1783 | "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", 1784 | "dev": true, 1785 | "dependencies": { 1786 | "irregular-plurals": "^3.3.0" 1787 | }, 1788 | "engines": { 1789 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1790 | }, 1791 | "funding": { 1792 | "url": "https://github.com/sponsors/sindresorhus" 1793 | } 1794 | }, 1795 | "node_modules/prettier": { 1796 | "version": "3.2.2", 1797 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.2.tgz", 1798 | "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==", 1799 | "dev": true, 1800 | "bin": { 1801 | "prettier": "bin/prettier.cjs" 1802 | }, 1803 | "engines": { 1804 | "node": ">=14" 1805 | }, 1806 | "funding": { 1807 | "url": "https://github.com/prettier/prettier?sponsor=1" 1808 | } 1809 | }, 1810 | "node_modules/pretty-ms": { 1811 | "version": "8.0.0", 1812 | "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", 1813 | "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", 1814 | "dev": true, 1815 | "dependencies": { 1816 | "parse-ms": "^3.0.0" 1817 | }, 1818 | "engines": { 1819 | "node": ">=14.16" 1820 | }, 1821 | "funding": { 1822 | "url": "https://github.com/sponsors/sindresorhus" 1823 | } 1824 | }, 1825 | "node_modules/queue-microtask": { 1826 | "version": "1.2.3", 1827 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1828 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1829 | "dev": true, 1830 | "funding": [ 1831 | { 1832 | "type": "github", 1833 | "url": "https://github.com/sponsors/feross" 1834 | }, 1835 | { 1836 | "type": "patreon", 1837 | "url": "https://www.patreon.com/feross" 1838 | }, 1839 | { 1840 | "type": "consulting", 1841 | "url": "https://feross.org/support" 1842 | } 1843 | ] 1844 | }, 1845 | "node_modules/readable-stream": { 1846 | "version": "3.6.2", 1847 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1848 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1849 | "dev": true, 1850 | "dependencies": { 1851 | "inherits": "^2.0.3", 1852 | "string_decoder": "^1.1.1", 1853 | "util-deprecate": "^1.0.1" 1854 | }, 1855 | "engines": { 1856 | "node": ">= 6" 1857 | } 1858 | }, 1859 | "node_modules/realistic-structured-clone": { 1860 | "version": "1.0.1", 1861 | "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-1.0.1.tgz", 1862 | "integrity": "sha512-UnQGgXdxTg+vwonhBz6VvfNFeqn/DUk0ntL/rSrN1mBOR261ZzLP3LQAFSBfIytyZYn4yue/64pZ7aN0x/RpiQ==", 1863 | "dev": true, 1864 | "dependencies": { 1865 | "lodash.isplainobject": "^3.0.2" 1866 | } 1867 | }, 1868 | "node_modules/require-directory": { 1869 | "version": "2.1.1", 1870 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1871 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 1872 | "dev": true, 1873 | "engines": { 1874 | "node": ">=0.10.0" 1875 | } 1876 | }, 1877 | "node_modules/resolve-cwd": { 1878 | "version": "3.0.0", 1879 | "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", 1880 | "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", 1881 | "dev": true, 1882 | "dependencies": { 1883 | "resolve-from": "^5.0.0" 1884 | }, 1885 | "engines": { 1886 | "node": ">=8" 1887 | } 1888 | }, 1889 | "node_modules/resolve-from": { 1890 | "version": "5.0.0", 1891 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", 1892 | "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", 1893 | "dev": true, 1894 | "engines": { 1895 | "node": ">=8" 1896 | } 1897 | }, 1898 | "node_modules/reusify": { 1899 | "version": "1.0.4", 1900 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1901 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1902 | "dev": true, 1903 | "engines": { 1904 | "iojs": ">=1.0.0", 1905 | "node": ">=0.10.0" 1906 | } 1907 | }, 1908 | "node_modules/rimraf": { 1909 | "version": "3.0.2", 1910 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1911 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1912 | "dev": true, 1913 | "dependencies": { 1914 | "glob": "^7.1.3" 1915 | }, 1916 | "bin": { 1917 | "rimraf": "bin.js" 1918 | }, 1919 | "funding": { 1920 | "url": "https://github.com/sponsors/isaacs" 1921 | } 1922 | }, 1923 | "node_modules/run-parallel": { 1924 | "version": "1.2.0", 1925 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1926 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1927 | "dev": true, 1928 | "funding": [ 1929 | { 1930 | "type": "github", 1931 | "url": "https://github.com/sponsors/feross" 1932 | }, 1933 | { 1934 | "type": "patreon", 1935 | "url": "https://www.patreon.com/feross" 1936 | }, 1937 | { 1938 | "type": "consulting", 1939 | "url": "https://feross.org/support" 1940 | } 1941 | ], 1942 | "dependencies": { 1943 | "queue-microtask": "^1.2.2" 1944 | } 1945 | }, 1946 | "node_modules/safe-buffer": { 1947 | "version": "5.2.1", 1948 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1949 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1950 | "dev": true, 1951 | "funding": [ 1952 | { 1953 | "type": "github", 1954 | "url": "https://github.com/sponsors/feross" 1955 | }, 1956 | { 1957 | "type": "patreon", 1958 | "url": "https://www.patreon.com/feross" 1959 | }, 1960 | { 1961 | "type": "consulting", 1962 | "url": "https://feross.org/support" 1963 | } 1964 | ] 1965 | }, 1966 | "node_modules/semver": { 1967 | "version": "7.5.4", 1968 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1969 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1970 | "dev": true, 1971 | "dependencies": { 1972 | "lru-cache": "^6.0.0" 1973 | }, 1974 | "bin": { 1975 | "semver": "bin/semver.js" 1976 | }, 1977 | "engines": { 1978 | "node": ">=10" 1979 | } 1980 | }, 1981 | "node_modules/serialize-error": { 1982 | "version": "7.0.1", 1983 | "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", 1984 | "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", 1985 | "dev": true, 1986 | "dependencies": { 1987 | "type-fest": "^0.13.1" 1988 | }, 1989 | "engines": { 1990 | "node": ">=10" 1991 | }, 1992 | "funding": { 1993 | "url": "https://github.com/sponsors/sindresorhus" 1994 | } 1995 | }, 1996 | "node_modules/service-worker-mock": { 1997 | "version": "2.0.5", 1998 | "resolved": "https://registry.npmjs.org/service-worker-mock/-/service-worker-mock-2.0.5.tgz", 1999 | "integrity": "sha512-yk6NCFnRWGfbOlP+IS4hEbJnGU8dVgtodAAKLxhkTPsOmaES44XVSWTNozK6KwI+p/0PDRrFsb2RjTMhvXiNkA==", 2000 | "dev": true, 2001 | "dependencies": { 2002 | "dom-urls": "^1.1.0", 2003 | "shelving-mock-indexeddb": "^1.1.0", 2004 | "url-search-params": "^0.10.0", 2005 | "w3c-hr-time": "^1.0.1" 2006 | } 2007 | }, 2008 | "node_modules/set-blocking": { 2009 | "version": "2.0.0", 2010 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 2011 | "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", 2012 | "dev": true 2013 | }, 2014 | "node_modules/shebang-command": { 2015 | "version": "2.0.0", 2016 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2017 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2018 | "dev": true, 2019 | "dependencies": { 2020 | "shebang-regex": "^3.0.0" 2021 | }, 2022 | "engines": { 2023 | "node": ">=8" 2024 | } 2025 | }, 2026 | "node_modules/shebang-regex": { 2027 | "version": "3.0.0", 2028 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2029 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2030 | "dev": true, 2031 | "engines": { 2032 | "node": ">=8" 2033 | } 2034 | }, 2035 | "node_modules/shelving-mock-event": { 2036 | "version": "1.0.12", 2037 | "resolved": "https://registry.npmjs.org/shelving-mock-event/-/shelving-mock-event-1.0.12.tgz", 2038 | "integrity": "sha512-2F+IZ010rwV3sA/Kd2hnC1vGNycsxeBJmjkXR8+4IOlv5e+Wvj+xH+A8Cv8/Z0lUyCut/HcxSpeDccYTVtnuaQ==", 2039 | "dev": true, 2040 | "engines": { 2041 | "node": ">=6.0.0" 2042 | } 2043 | }, 2044 | "node_modules/shelving-mock-indexeddb": { 2045 | "version": "1.1.0", 2046 | "resolved": "https://registry.npmjs.org/shelving-mock-indexeddb/-/shelving-mock-indexeddb-1.1.0.tgz", 2047 | "integrity": "sha512-akHJAmGL/dplJ4FZNxPxVbOxMw8Ey6wAnB9+3+GCUNqPUcJaskS55GijxZtarTfAYB4XQyu+FLtjcq2Oa3e2Lg==", 2048 | "dev": true, 2049 | "dependencies": { 2050 | "realistic-structured-clone": "^1.0.1", 2051 | "shelving-mock-event": "^1.0.12" 2052 | }, 2053 | "engines": { 2054 | "node": ">=6.0.0" 2055 | } 2056 | }, 2057 | "node_modules/signal-exit": { 2058 | "version": "3.0.7", 2059 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 2060 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 2061 | "dev": true 2062 | }, 2063 | "node_modules/slash": { 2064 | "version": "5.1.0", 2065 | "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", 2066 | "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", 2067 | "dev": true, 2068 | "engines": { 2069 | "node": ">=14.16" 2070 | }, 2071 | "funding": { 2072 | "url": "https://github.com/sponsors/sindresorhus" 2073 | } 2074 | }, 2075 | "node_modules/slice-ansi": { 2076 | "version": "5.0.0", 2077 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", 2078 | "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", 2079 | "dev": true, 2080 | "dependencies": { 2081 | "ansi-styles": "^6.0.0", 2082 | "is-fullwidth-code-point": "^4.0.0" 2083 | }, 2084 | "engines": { 2085 | "node": ">=12" 2086 | }, 2087 | "funding": { 2088 | "url": "https://github.com/chalk/slice-ansi?sponsor=1" 2089 | } 2090 | }, 2091 | "node_modules/sprintf-js": { 2092 | "version": "1.0.3", 2093 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 2094 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 2095 | "dev": true 2096 | }, 2097 | "node_modules/stack-utils": { 2098 | "version": "2.0.6", 2099 | "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", 2100 | "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", 2101 | "dev": true, 2102 | "dependencies": { 2103 | "escape-string-regexp": "^2.0.0" 2104 | }, 2105 | "engines": { 2106 | "node": ">=10" 2107 | } 2108 | }, 2109 | "node_modules/stack-utils/node_modules/escape-string-regexp": { 2110 | "version": "2.0.0", 2111 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", 2112 | "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", 2113 | "dev": true, 2114 | "engines": { 2115 | "node": ">=8" 2116 | } 2117 | }, 2118 | "node_modules/string_decoder": { 2119 | "version": "1.3.0", 2120 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 2121 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 2122 | "dev": true, 2123 | "dependencies": { 2124 | "safe-buffer": "~5.2.0" 2125 | } 2126 | }, 2127 | "node_modules/string-width": { 2128 | "version": "7.0.0", 2129 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", 2130 | "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", 2131 | "dev": true, 2132 | "dependencies": { 2133 | "emoji-regex": "^10.3.0", 2134 | "get-east-asian-width": "^1.0.0", 2135 | "strip-ansi": "^7.1.0" 2136 | }, 2137 | "engines": { 2138 | "node": ">=18" 2139 | }, 2140 | "funding": { 2141 | "url": "https://github.com/sponsors/sindresorhus" 2142 | } 2143 | }, 2144 | "node_modules/strip-ansi": { 2145 | "version": "7.1.0", 2146 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 2147 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 2148 | "dev": true, 2149 | "dependencies": { 2150 | "ansi-regex": "^6.0.1" 2151 | }, 2152 | "engines": { 2153 | "node": ">=12" 2154 | }, 2155 | "funding": { 2156 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 2157 | } 2158 | }, 2159 | "node_modules/strip-final-newline": { 2160 | "version": "3.0.0", 2161 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", 2162 | "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", 2163 | "dev": true, 2164 | "engines": { 2165 | "node": ">=12" 2166 | }, 2167 | "funding": { 2168 | "url": "https://github.com/sponsors/sindresorhus" 2169 | } 2170 | }, 2171 | "node_modules/supertap": { 2172 | "version": "3.0.1", 2173 | "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", 2174 | "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", 2175 | "dev": true, 2176 | "dependencies": { 2177 | "indent-string": "^5.0.0", 2178 | "js-yaml": "^3.14.1", 2179 | "serialize-error": "^7.0.1", 2180 | "strip-ansi": "^7.0.1" 2181 | }, 2182 | "engines": { 2183 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 2184 | } 2185 | }, 2186 | "node_modules/tar": { 2187 | "version": "6.2.0", 2188 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", 2189 | "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", 2190 | "dev": true, 2191 | "dependencies": { 2192 | "chownr": "^2.0.0", 2193 | "fs-minipass": "^2.0.0", 2194 | "minipass": "^5.0.0", 2195 | "minizlib": "^2.1.1", 2196 | "mkdirp": "^1.0.3", 2197 | "yallist": "^4.0.0" 2198 | }, 2199 | "engines": { 2200 | "node": ">=10" 2201 | } 2202 | }, 2203 | "node_modules/temp-dir": { 2204 | "version": "3.0.0", 2205 | "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", 2206 | "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", 2207 | "dev": true, 2208 | "engines": { 2209 | "node": ">=14.16" 2210 | } 2211 | }, 2212 | "node_modules/time-zone": { 2213 | "version": "1.0.0", 2214 | "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", 2215 | "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", 2216 | "dev": true, 2217 | "engines": { 2218 | "node": ">=4" 2219 | } 2220 | }, 2221 | "node_modules/to-regex-range": { 2222 | "version": "5.0.1", 2223 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2224 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2225 | "dev": true, 2226 | "dependencies": { 2227 | "is-number": "^7.0.0" 2228 | }, 2229 | "engines": { 2230 | "node": ">=8.0" 2231 | } 2232 | }, 2233 | "node_modules/tr46": { 2234 | "version": "0.0.3", 2235 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 2236 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 2237 | "dev": true 2238 | }, 2239 | "node_modules/type-fest": { 2240 | "version": "0.13.1", 2241 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", 2242 | "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", 2243 | "dev": true, 2244 | "engines": { 2245 | "node": ">=10" 2246 | }, 2247 | "funding": { 2248 | "url": "https://github.com/sponsors/sindresorhus" 2249 | } 2250 | }, 2251 | "node_modules/typescript": { 2252 | "version": "5.3.3", 2253 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 2254 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 2255 | "dev": true, 2256 | "bin": { 2257 | "tsc": "bin/tsc", 2258 | "tsserver": "bin/tsserver" 2259 | }, 2260 | "engines": { 2261 | "node": ">=14.17" 2262 | } 2263 | }, 2264 | "node_modules/undici-types": { 2265 | "version": "5.26.5", 2266 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 2267 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 2268 | "dev": true 2269 | }, 2270 | "node_modules/unicorn-magic": { 2271 | "version": "0.1.0", 2272 | "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", 2273 | "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", 2274 | "dev": true, 2275 | "engines": { 2276 | "node": ">=18" 2277 | }, 2278 | "funding": { 2279 | "url": "https://github.com/sponsors/sindresorhus" 2280 | } 2281 | }, 2282 | "node_modules/urijs": { 2283 | "version": "1.19.11", 2284 | "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", 2285 | "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", 2286 | "dev": true 2287 | }, 2288 | "node_modules/url-search-params": { 2289 | "version": "0.10.2", 2290 | "resolved": "https://registry.npmjs.org/url-search-params/-/url-search-params-0.10.2.tgz", 2291 | "integrity": "sha512-d6GYsr992Bo9rzTZFc9BUw3UFAAg3prE9JGVBgW2TLTbI3rSvg4VDa0BFXHMzKkWbAuhrmaFWpucpRJl+3W7Jg==", 2292 | "deprecated": "now available as @ungap/url-search-params", 2293 | "dev": true 2294 | }, 2295 | "node_modules/util-deprecate": { 2296 | "version": "1.0.2", 2297 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2298 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 2299 | "dev": true 2300 | }, 2301 | "node_modules/w3c-hr-time": { 2302 | "version": "1.0.2", 2303 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", 2304 | "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", 2305 | "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", 2306 | "dev": true, 2307 | "dependencies": { 2308 | "browser-process-hrtime": "^1.0.0" 2309 | } 2310 | }, 2311 | "node_modules/webidl-conversions": { 2312 | "version": "3.0.1", 2313 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 2314 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 2315 | "dev": true 2316 | }, 2317 | "node_modules/well-known-symbols": { 2318 | "version": "2.0.0", 2319 | "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", 2320 | "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", 2321 | "dev": true, 2322 | "engines": { 2323 | "node": ">=6" 2324 | } 2325 | }, 2326 | "node_modules/whatwg-url": { 2327 | "version": "5.0.0", 2328 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 2329 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 2330 | "dev": true, 2331 | "dependencies": { 2332 | "tr46": "~0.0.3", 2333 | "webidl-conversions": "^3.0.0" 2334 | } 2335 | }, 2336 | "node_modules/which": { 2337 | "version": "2.0.2", 2338 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2339 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2340 | "dev": true, 2341 | "dependencies": { 2342 | "isexe": "^2.0.0" 2343 | }, 2344 | "bin": { 2345 | "node-which": "bin/node-which" 2346 | }, 2347 | "engines": { 2348 | "node": ">= 8" 2349 | } 2350 | }, 2351 | "node_modules/wide-align": { 2352 | "version": "1.1.5", 2353 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 2354 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 2355 | "dev": true, 2356 | "dependencies": { 2357 | "string-width": "^1.0.2 || 2 || 3 || 4" 2358 | } 2359 | }, 2360 | "node_modules/wide-align/node_modules/ansi-regex": { 2361 | "version": "5.0.1", 2362 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2363 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2364 | "dev": true, 2365 | "engines": { 2366 | "node": ">=8" 2367 | } 2368 | }, 2369 | "node_modules/wide-align/node_modules/emoji-regex": { 2370 | "version": "8.0.0", 2371 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2372 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2373 | "dev": true 2374 | }, 2375 | "node_modules/wide-align/node_modules/is-fullwidth-code-point": { 2376 | "version": "3.0.0", 2377 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2378 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2379 | "dev": true, 2380 | "engines": { 2381 | "node": ">=8" 2382 | } 2383 | }, 2384 | "node_modules/wide-align/node_modules/string-width": { 2385 | "version": "4.2.3", 2386 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2387 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2388 | "dev": true, 2389 | "dependencies": { 2390 | "emoji-regex": "^8.0.0", 2391 | "is-fullwidth-code-point": "^3.0.0", 2392 | "strip-ansi": "^6.0.1" 2393 | }, 2394 | "engines": { 2395 | "node": ">=8" 2396 | } 2397 | }, 2398 | "node_modules/wide-align/node_modules/strip-ansi": { 2399 | "version": "6.0.1", 2400 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2401 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2402 | "dev": true, 2403 | "dependencies": { 2404 | "ansi-regex": "^5.0.1" 2405 | }, 2406 | "engines": { 2407 | "node": ">=8" 2408 | } 2409 | }, 2410 | "node_modules/wrap-ansi": { 2411 | "version": "7.0.0", 2412 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2413 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2414 | "dev": true, 2415 | "dependencies": { 2416 | "ansi-styles": "^4.0.0", 2417 | "string-width": "^4.1.0", 2418 | "strip-ansi": "^6.0.0" 2419 | }, 2420 | "engines": { 2421 | "node": ">=10" 2422 | }, 2423 | "funding": { 2424 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 2425 | } 2426 | }, 2427 | "node_modules/wrap-ansi/node_modules/ansi-regex": { 2428 | "version": "5.0.1", 2429 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2430 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2431 | "dev": true, 2432 | "engines": { 2433 | "node": ">=8" 2434 | } 2435 | }, 2436 | "node_modules/wrap-ansi/node_modules/ansi-styles": { 2437 | "version": "4.3.0", 2438 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 2439 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 2440 | "dev": true, 2441 | "dependencies": { 2442 | "color-convert": "^2.0.1" 2443 | }, 2444 | "engines": { 2445 | "node": ">=8" 2446 | }, 2447 | "funding": { 2448 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2449 | } 2450 | }, 2451 | "node_modules/wrap-ansi/node_modules/emoji-regex": { 2452 | "version": "8.0.0", 2453 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2454 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2455 | "dev": true 2456 | }, 2457 | "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { 2458 | "version": "3.0.0", 2459 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2460 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2461 | "dev": true, 2462 | "engines": { 2463 | "node": ">=8" 2464 | } 2465 | }, 2466 | "node_modules/wrap-ansi/node_modules/string-width": { 2467 | "version": "4.2.3", 2468 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2469 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2470 | "dev": true, 2471 | "dependencies": { 2472 | "emoji-regex": "^8.0.0", 2473 | "is-fullwidth-code-point": "^3.0.0", 2474 | "strip-ansi": "^6.0.1" 2475 | }, 2476 | "engines": { 2477 | "node": ">=8" 2478 | } 2479 | }, 2480 | "node_modules/wrap-ansi/node_modules/strip-ansi": { 2481 | "version": "6.0.1", 2482 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2483 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2484 | "dev": true, 2485 | "dependencies": { 2486 | "ansi-regex": "^5.0.1" 2487 | }, 2488 | "engines": { 2489 | "node": ">=8" 2490 | } 2491 | }, 2492 | "node_modules/wrappy": { 2493 | "version": "1.0.2", 2494 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2495 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2496 | "dev": true 2497 | }, 2498 | "node_modules/write-file-atomic": { 2499 | "version": "5.0.1", 2500 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", 2501 | "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", 2502 | "dev": true, 2503 | "dependencies": { 2504 | "imurmurhash": "^0.1.4", 2505 | "signal-exit": "^4.0.1" 2506 | }, 2507 | "engines": { 2508 | "node": "^14.17.0 || ^16.13.0 || >=18.0.0" 2509 | } 2510 | }, 2511 | "node_modules/write-file-atomic/node_modules/signal-exit": { 2512 | "version": "4.1.0", 2513 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 2514 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 2515 | "dev": true, 2516 | "engines": { 2517 | "node": ">=14" 2518 | }, 2519 | "funding": { 2520 | "url": "https://github.com/sponsors/isaacs" 2521 | } 2522 | }, 2523 | "node_modules/y18n": { 2524 | "version": "5.0.8", 2525 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 2526 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 2527 | "dev": true, 2528 | "engines": { 2529 | "node": ">=10" 2530 | } 2531 | }, 2532 | "node_modules/yallist": { 2533 | "version": "4.0.0", 2534 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2535 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 2536 | "dev": true 2537 | }, 2538 | "node_modules/yargs": { 2539 | "version": "17.7.2", 2540 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 2541 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 2542 | "dev": true, 2543 | "dependencies": { 2544 | "cliui": "^8.0.1", 2545 | "escalade": "^3.1.1", 2546 | "get-caller-file": "^2.0.5", 2547 | "require-directory": "^2.1.1", 2548 | "string-width": "^4.2.3", 2549 | "y18n": "^5.0.5", 2550 | "yargs-parser": "^21.1.1" 2551 | }, 2552 | "engines": { 2553 | "node": ">=12" 2554 | } 2555 | }, 2556 | "node_modules/yargs-parser": { 2557 | "version": "21.1.1", 2558 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 2559 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 2560 | "dev": true, 2561 | "engines": { 2562 | "node": ">=12" 2563 | } 2564 | }, 2565 | "node_modules/yargs/node_modules/ansi-regex": { 2566 | "version": "5.0.1", 2567 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2568 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2569 | "dev": true, 2570 | "engines": { 2571 | "node": ">=8" 2572 | } 2573 | }, 2574 | "node_modules/yargs/node_modules/emoji-regex": { 2575 | "version": "8.0.0", 2576 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 2577 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 2578 | "dev": true 2579 | }, 2580 | "node_modules/yargs/node_modules/is-fullwidth-code-point": { 2581 | "version": "3.0.0", 2582 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2583 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2584 | "dev": true, 2585 | "engines": { 2586 | "node": ">=8" 2587 | } 2588 | }, 2589 | "node_modules/yargs/node_modules/string-width": { 2590 | "version": "4.2.3", 2591 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2592 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2593 | "dev": true, 2594 | "dependencies": { 2595 | "emoji-regex": "^8.0.0", 2596 | "is-fullwidth-code-point": "^3.0.0", 2597 | "strip-ansi": "^6.0.1" 2598 | }, 2599 | "engines": { 2600 | "node": ">=8" 2601 | } 2602 | }, 2603 | "node_modules/yargs/node_modules/strip-ansi": { 2604 | "version": "6.0.1", 2605 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2606 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2607 | "dev": true, 2608 | "dependencies": { 2609 | "ansi-regex": "^5.0.1" 2610 | }, 2611 | "engines": { 2612 | "node": ">=8" 2613 | } 2614 | } 2615 | } 2616 | } 2617 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudflare/kv-asset-handler", 3 | "version": "0.3.1", 4 | "description": "Routes requests to KV assets", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "prepack": "npm run build", 9 | "build": "tsc -d", 10 | "format": "prettier --write \"**/*.{js,ts,json,md}\"", 11 | "pretest": "npm run build", 12 | "lint:code": "prettier --check \"**/*.{js,ts,json,md}\"", 13 | "lint:markdown": "markdownlint \"**/*.md\" --ignore node_modules", 14 | "test": "ava dist/test/*.js --verbose" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/cloudflare/kv-asset-handler.git" 19 | }, 20 | "keywords": [ 21 | "kv", 22 | "cloudflare", 23 | "workers", 24 | "wrangler", 25 | "assets" 26 | ], 27 | "files": [ 28 | "src", 29 | "dist", 30 | "!src/test", 31 | "!dist/test", 32 | "LICENSE_APACHE", 33 | "LICENSE_MIT" 34 | ], 35 | "author": "wrangler@cloudflare.com", 36 | "license": "MIT OR Apache-2.0", 37 | "bugs": { 38 | "url": "https://github.com/cloudflare/kv-asset-handler/issues" 39 | }, 40 | "homepage": "https://github.com/cloudflare/kv-asset-handler#readme", 41 | "dependencies": { 42 | "mime": "^3.0.0" 43 | }, 44 | "devDependencies": { 45 | "@ava/typescript": "^4.1.0", 46 | "@cloudflare/workers-types": "^4.20231218.0", 47 | "@types/mime": "^3.0.4", 48 | "@types/node": "^18.11.12", 49 | "ava": "^6.0.1", 50 | "prettier": "^3.2.2", 51 | "service-worker-mock": "^2.0.5", 52 | "typescript": "^5.3.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as mime from 'mime' 2 | import { 3 | Options, 4 | CacheControl, 5 | MethodNotAllowedError, 6 | NotFoundError, 7 | InternalError, 8 | AssetManifestType, 9 | } from './types' 10 | 11 | declare global { 12 | var __STATIC_CONTENT: any, __STATIC_CONTENT_MANIFEST: string 13 | } 14 | 15 | const defaultCacheControl: CacheControl = { 16 | browserTTL: null, 17 | edgeTTL: 2 * 60 * 60 * 24, // 2 days 18 | bypassCache: false, // do not bypass Cloudflare's cache 19 | } 20 | 21 | const parseStringAsObject = (maybeString: string | T): T => 22 | typeof maybeString === 'string' ? (JSON.parse(maybeString) as T) : maybeString 23 | 24 | const getAssetFromKVDefaultOptions: Partial = { 25 | ASSET_NAMESPACE: typeof __STATIC_CONTENT !== 'undefined' ? __STATIC_CONTENT : undefined, 26 | ASSET_MANIFEST: 27 | typeof __STATIC_CONTENT_MANIFEST !== 'undefined' 28 | ? parseStringAsObject(__STATIC_CONTENT_MANIFEST) 29 | : {}, 30 | cacheControl: defaultCacheControl, 31 | defaultMimeType: 'text/plain', 32 | defaultDocument: 'index.html', 33 | pathIsEncoded: false, 34 | defaultETag: 'strong', 35 | } 36 | 37 | function assignOptions(options?: Partial): Options { 38 | // Assign any missing options passed in to the default 39 | // options.mapRequestToAsset is handled manually later 40 | return Object.assign({}, getAssetFromKVDefaultOptions, options) 41 | } 42 | 43 | /** 44 | * maps the path of incoming request to the request pathKey to look up 45 | * in bucket and in cache 46 | * e.g. for a path '/' returns '/index.html' which serves 47 | * the content of bucket/index.html 48 | * @param {Request} request incoming request 49 | */ 50 | const mapRequestToAsset = (request: Request, options?: Partial) => { 51 | options = assignOptions(options) 52 | 53 | const parsedUrl = new URL(request.url) 54 | let pathname = parsedUrl.pathname 55 | 56 | if (pathname.endsWith('/')) { 57 | // If path looks like a directory append options.defaultDocument 58 | // e.g. If path is /about/ -> /about/index.html 59 | pathname = pathname.concat(options.defaultDocument) 60 | } else if (!mime.getType(pathname)) { 61 | // If path doesn't look like valid content 62 | // e.g. /about.me -> /about.me/index.html 63 | pathname = pathname.concat('/' + options.defaultDocument) 64 | } 65 | 66 | parsedUrl.pathname = pathname 67 | return new Request(parsedUrl.toString(), request) 68 | } 69 | 70 | /** 71 | * maps the path of incoming request to /index.html if it evaluates to 72 | * any HTML file. 73 | * @param {Request} request incoming request 74 | */ 75 | function serveSinglePageApp(request: Request, options?: Partial): Request { 76 | options = assignOptions(options) 77 | 78 | // First apply the default handler, which already has logic to detect 79 | // paths that should map to HTML files. 80 | request = mapRequestToAsset(request, options) 81 | 82 | const parsedUrl = new URL(request.url) 83 | 84 | // Detect if the default handler decided to map to 85 | // a HTML file in some specific directory. 86 | if (parsedUrl.pathname.endsWith('.html')) { 87 | // If expected HTML file was missing, just return the root index.html (or options.defaultDocument) 88 | return new Request(`${parsedUrl.origin}/${options.defaultDocument}`, request) 89 | } else { 90 | // The default handler decided this is not an HTML page. It's probably 91 | // an image, CSS, or JS file. Leave it as-is. 92 | return request 93 | } 94 | } 95 | 96 | /** 97 | * takes the path of the incoming request, gathers the appropriate content from KV, and returns 98 | * the response 99 | * 100 | * @param {FetchEvent} event the fetch event of the triggered request 101 | * @param {{mapRequestToAsset: (string: Request) => Request, cacheControl: {bypassCache:boolean, edgeTTL: number, browserTTL:number}, ASSET_NAMESPACE: any, ASSET_MANIFEST:any}} [options] configurable options 102 | * @param {CacheControl} [options.cacheControl] determine how to cache on Cloudflare and the browser 103 | * @param {typeof(options.mapRequestToAsset)} [options.mapRequestToAsset] maps the path of incoming request to the request pathKey to look up 104 | * @param {Object | string} [options.ASSET_NAMESPACE] the binding to the namespace that script references 105 | * @param {any} [options.ASSET_MANIFEST] the map of the key to cache and store in KV 106 | * */ 107 | 108 | type Evt = { 109 | request: Request 110 | waitUntil: (promise: Promise) => void 111 | } 112 | 113 | const getAssetFromKV = async (event: Evt, options?: Partial): Promise => { 114 | options = assignOptions(options) 115 | 116 | const request = event.request 117 | const ASSET_NAMESPACE = options.ASSET_NAMESPACE 118 | const ASSET_MANIFEST = parseStringAsObject(options.ASSET_MANIFEST) 119 | 120 | if (typeof ASSET_NAMESPACE === 'undefined') { 121 | throw new InternalError(`there is no KV namespace bound to the script`) 122 | } 123 | 124 | const rawPathKey = new URL(request.url).pathname.replace(/^\/+/, '') // strip any preceding /'s 125 | let pathIsEncoded = options.pathIsEncoded 126 | let requestKey 127 | // if options.mapRequestToAsset is explicitly passed in, always use it and assume user has own intentions 128 | // otherwise handle request as normal, with default mapRequestToAsset below 129 | if (options.mapRequestToAsset) { 130 | requestKey = options.mapRequestToAsset(request) 131 | } else if (ASSET_MANIFEST[rawPathKey]) { 132 | requestKey = request 133 | } else if (ASSET_MANIFEST[decodeURIComponent(rawPathKey)]) { 134 | pathIsEncoded = true 135 | requestKey = request 136 | } else { 137 | const mappedRequest = mapRequestToAsset(request) 138 | const mappedRawPathKey = new URL(mappedRequest.url).pathname.replace(/^\/+/, '') 139 | if (ASSET_MANIFEST[decodeURIComponent(mappedRawPathKey)]) { 140 | pathIsEncoded = true 141 | requestKey = mappedRequest 142 | } else { 143 | // use default mapRequestToAsset 144 | requestKey = mapRequestToAsset(request, options) 145 | } 146 | } 147 | 148 | const SUPPORTED_METHODS = ['GET', 'HEAD'] 149 | if (!SUPPORTED_METHODS.includes(requestKey.method)) { 150 | throw new MethodNotAllowedError(`${requestKey.method} is not a valid request method`) 151 | } 152 | 153 | const parsedUrl = new URL(requestKey.url) 154 | const pathname = pathIsEncoded ? decodeURIComponent(parsedUrl.pathname) : parsedUrl.pathname // decode percentage encoded path only when necessary 155 | 156 | // pathKey is the file path to look up in the manifest 157 | let pathKey = pathname.replace(/^\/+/, '') // remove prepended / 158 | 159 | // @ts-ignore 160 | const cache = caches.default 161 | let mimeType = mime.getType(pathKey) || options.defaultMimeType 162 | if (mimeType.startsWith('text') || mimeType === 'application/javascript') { 163 | mimeType += '; charset=utf-8' 164 | } 165 | 166 | let shouldEdgeCache = false // false if storing in KV by raw file path i.e. no hash 167 | // check manifest for map from file path to hash 168 | if (typeof ASSET_MANIFEST !== 'undefined') { 169 | if (ASSET_MANIFEST[pathKey]) { 170 | pathKey = ASSET_MANIFEST[pathKey] 171 | // if path key is in asset manifest, we can assume it contains a content hash and can be cached 172 | shouldEdgeCache = true 173 | } 174 | } 175 | 176 | // TODO this excludes search params from cache, investigate ideal behavior 177 | let cacheKey = new Request(`${parsedUrl.origin}/${pathKey}`, request) 178 | 179 | // if argument passed in for cacheControl is a function then 180 | // evaluate that function. otherwise return the Object passed in 181 | // or default Object 182 | const evalCacheOpts = (() => { 183 | switch (typeof options.cacheControl) { 184 | case 'function': 185 | return options.cacheControl(request) 186 | case 'object': 187 | return options.cacheControl 188 | default: 189 | return defaultCacheControl 190 | } 191 | })() 192 | 193 | // formats the etag depending on the response context. if the entityId 194 | // is invalid, returns an empty string (instead of null) to prevent the 195 | // the potentially disastrous scenario where the value of the Etag resp 196 | // header is "null". Could be modified in future to base64 encode etc 197 | const formatETag = (entityId: any = pathKey, validatorType: string = options.defaultETag) => { 198 | if (!entityId) { 199 | return '' 200 | } 201 | switch (validatorType) { 202 | case 'weak': 203 | if (!entityId.startsWith('W/')) { 204 | if (entityId.startsWith(`"`) && entityId.endsWith(`"`)) { 205 | return `W/${entityId}` 206 | } 207 | return `W/"${entityId}"` 208 | } 209 | return entityId 210 | case 'strong': 211 | if (entityId.startsWith(`W/"`)) { 212 | entityId = entityId.replace('W/', '') 213 | } 214 | if (!entityId.endsWith(`"`)) { 215 | entityId = `"${entityId}"` 216 | } 217 | return entityId 218 | default: 219 | return '' 220 | } 221 | } 222 | 223 | options.cacheControl = Object.assign({}, defaultCacheControl, evalCacheOpts) 224 | 225 | // override shouldEdgeCache if options say to bypassCache 226 | if ( 227 | options.cacheControl.bypassCache || 228 | options.cacheControl.edgeTTL === null || 229 | request.method == 'HEAD' 230 | ) { 231 | shouldEdgeCache = false 232 | } 233 | // only set max-age if explicitly passed in a number as an arg 234 | const shouldSetBrowserCache = typeof options.cacheControl.browserTTL === 'number' 235 | 236 | let response = null 237 | if (shouldEdgeCache) { 238 | response = await cache.match(cacheKey) 239 | } 240 | 241 | if (response) { 242 | if (response.status > 300 && response.status < 400) { 243 | if (response.body && 'cancel' in Object.getPrototypeOf(response.body)) { 244 | // Body exists and environment supports readable streams 245 | response.body.cancel() 246 | } else { 247 | // Environment doesnt support readable streams, or null repsonse body. Nothing to do 248 | } 249 | response = new Response(null, response) 250 | } else { 251 | // fixes #165 252 | let opts = { 253 | headers: new Headers(response.headers), 254 | status: 0, 255 | statusText: '', 256 | } 257 | 258 | opts.headers.set('cf-cache-status', 'HIT') 259 | 260 | if (response.status) { 261 | opts.status = response.status 262 | opts.statusText = response.statusText 263 | } else if (opts.headers.has('Content-Range')) { 264 | opts.status = 206 265 | opts.statusText = 'Partial Content' 266 | } else { 267 | opts.status = 200 268 | opts.statusText = 'OK' 269 | } 270 | response = new Response(response.body, opts) 271 | } 272 | } else { 273 | const body = await ASSET_NAMESPACE.get(pathKey, 'arrayBuffer') 274 | if (body === null) { 275 | throw new NotFoundError(`could not find ${pathKey} in your content namespace`) 276 | } 277 | response = new Response(body) 278 | 279 | if (shouldEdgeCache) { 280 | response.headers.set('Accept-Ranges', 'bytes') 281 | response.headers.set('Content-Length', String(body.byteLength)) 282 | // set etag before cache insertion 283 | if (!response.headers.has('etag')) { 284 | response.headers.set('etag', formatETag(pathKey)) 285 | } 286 | // determine Cloudflare cache behavior 287 | response.headers.set('Cache-Control', `max-age=${options.cacheControl.edgeTTL}`) 288 | event.waitUntil(cache.put(cacheKey, response.clone())) 289 | response.headers.set('CF-Cache-Status', 'MISS') 290 | } 291 | } 292 | response.headers.set('Content-Type', mimeType) 293 | 294 | if (response.status === 304) { 295 | let etag = formatETag(response.headers.get('etag')) 296 | let ifNoneMatch = cacheKey.headers.get('if-none-match') 297 | let proxyCacheStatus = response.headers.get('CF-Cache-Status') 298 | if (etag) { 299 | if (ifNoneMatch && ifNoneMatch === etag && proxyCacheStatus === 'MISS') { 300 | response.headers.set('CF-Cache-Status', 'EXPIRED') 301 | } else { 302 | response.headers.set('CF-Cache-Status', 'REVALIDATED') 303 | } 304 | response.headers.set('etag', formatETag(etag, 'weak')) 305 | } 306 | } 307 | if (shouldSetBrowserCache) { 308 | response.headers.set('Cache-Control', `max-age=${options.cacheControl.browserTTL}`) 309 | } else { 310 | response.headers.delete('Cache-Control') 311 | } 312 | return response 313 | } 314 | 315 | export { getAssetFromKV, mapRequestToAsset, serveSinglePageApp } 316 | export { Options, CacheControl, MethodNotAllowedError, NotFoundError, InternalError } 317 | -------------------------------------------------------------------------------- /src/mocks.ts: -------------------------------------------------------------------------------- 1 | const makeServiceWorkerEnv = require('service-worker-mock') 2 | 3 | const HASH = '123HASHBROWN' 4 | 5 | export const getEvent = (request: Request): any => { 6 | const waitUntil = async (callback: any) => { 7 | await callback 8 | } 9 | return { 10 | request, 11 | waitUntil, 12 | } 13 | } 14 | const store: any = { 15 | 'key1.123HASHBROWN.txt': 'val1', 16 | 'key1.123HASHBROWN.png': 'val1', 17 | 'index.123HASHBROWN.html': 'index.html', 18 | 'cache.123HASHBROWN.html': 'cache me if you can', 19 | '测试.123HASHBROWN.html': 'My filename is non-ascii', 20 | '%not-really-percent-encoded.123HASHBROWN.html': 'browser percent encoded', 21 | '%2F.123HASHBROWN.html': 'user percent encoded', 22 | '你好.123HASHBROWN.html': 'I shouldnt be served', 23 | '%E4%BD%A0%E5%A5%BD.123HASHBROWN.html': 'Im important', 24 | 'nohash.txt': 'no hash but still got some result', 25 | 'sub/blah.123HASHBROWN.png': 'picturedis', 26 | 'sub/index.123HASHBROWN.html': 'picturedis', 27 | 'client.123HASHBROWN': 'important file', 28 | 'client.123HASHBROWN/index.html': 'Im here but serve my big bro above', 29 | 'image.123HASHBROWN.png': 'imagepng', 30 | 'image.123HASHBROWN.webp': 'imagewebp', 31 | '你好/index.123HASHBROWN.html': 'My path is non-ascii', 32 | } 33 | export const mockKV = (store: any) => { 34 | return { 35 | get: (path: string) => store[path] || null, 36 | } 37 | } 38 | 39 | export const mockManifest = () => { 40 | return JSON.stringify({ 41 | 'key1.txt': `key1.${HASH}.txt`, 42 | 'key1.png': `key1.${HASH}.png`, 43 | 'cache.html': `cache.${HASH}.html`, 44 | '测试.html': `测试.${HASH}.html`, 45 | '你好.html': `你好.${HASH}.html`, 46 | '%not-really-percent-encoded.html': `%not-really-percent-encoded.${HASH}.html`, 47 | '%2F.html': `%2F.${HASH}.html`, 48 | '%E4%BD%A0%E5%A5%BD.html': `%E4%BD%A0%E5%A5%BD.${HASH}.html`, 49 | 'index.html': `index.${HASH}.html`, 50 | 'sub/blah.png': `sub/blah.${HASH}.png`, 51 | 'sub/index.html': `sub/index.${HASH}.html`, 52 | client: `client.${HASH}`, 53 | 'client/index.html': `client.${HASH}`, 54 | 'image.png': `image.${HASH}.png`, 55 | 'image.webp': `image.${HASH}.webp`, 56 | '你好/index.html': `你好/index.${HASH}.html`, 57 | }) 58 | } 59 | 60 | let cacheStore: any = new Map() 61 | interface CacheKey { 62 | url: object 63 | headers: object 64 | } 65 | export const mockCaches = () => { 66 | return { 67 | default: { 68 | async match(key: any) { 69 | let cacheKey: CacheKey = { 70 | url: key.url, 71 | headers: {}, 72 | } 73 | let response 74 | if (key.headers.has('if-none-match')) { 75 | let makeStrongEtag = key.headers.get('if-none-match').replace('W/', '') 76 | Reflect.set(cacheKey.headers, 'etag', makeStrongEtag) 77 | response = cacheStore.get(JSON.stringify(cacheKey)) 78 | } else { 79 | // if client doesn't send if-none-match, we need to iterate through these keys 80 | // and just test the URL 81 | const activeCacheKeys: Array = Array.from(cacheStore.keys()) 82 | for (const cacheStoreKey of activeCacheKeys) { 83 | if (JSON.parse(cacheStoreKey).url === key.url) { 84 | response = cacheStore.get(cacheStoreKey) 85 | } 86 | } 87 | } 88 | // TODO: write test to accomodate for rare scenarios with where range requests accomodate etags 89 | if (response && !key.headers.has('if-none-match')) { 90 | // this appears overly verbose, but is necessary to document edge cache behavior 91 | // The Range request header triggers the response header Content-Range ... 92 | const range = key.headers.get('range') 93 | if (range) { 94 | response.headers.set( 95 | 'content-range', 96 | `bytes ${range.split('=').pop()}/${response.headers.get('content-length')}`, 97 | ) 98 | } 99 | // ... which we are using in this repository to set status 206 100 | if (response.headers.has('content-range')) { 101 | response.status = 206 102 | } else { 103 | response.status = 200 104 | } 105 | let etag = response.headers.get('etag') 106 | if (etag && !etag.includes('W/')) { 107 | response.headers.set('etag', `W/${etag}`) 108 | } 109 | } 110 | return response 111 | }, 112 | async put(key: any, val: Response) { 113 | let headers = new Headers(val.headers) 114 | let url = new URL(key.url) 115 | let resWithBody = new Response(val.body, { headers, status: 200 }) 116 | let resNoBody = new Response(null, { headers, status: 304 }) 117 | let cacheKey: CacheKey = { 118 | url: key.url, 119 | headers: { 120 | etag: `"${url.pathname.replace('/', '')}"`, 121 | }, 122 | } 123 | cacheStore.set(JSON.stringify(cacheKey), resNoBody) 124 | cacheKey.headers = {} 125 | cacheStore.set(JSON.stringify(cacheKey), resWithBody) 126 | return 127 | }, 128 | }, 129 | } 130 | } 131 | 132 | // mocks functionality used inside worker request 133 | export function mockRequestScope() { 134 | Object.assign(global, makeServiceWorkerEnv()) 135 | Object.assign(global, { __STATIC_CONTENT_MANIFEST: mockManifest() }) 136 | Object.assign(global, { __STATIC_CONTENT: mockKV(store) }) 137 | Object.assign(global, { caches: mockCaches() }) 138 | } 139 | 140 | // mocks functionality used on global isolate scope. such as the KV namespace bind 141 | export function mockGlobalScope() { 142 | Object.assign(global, { __STATIC_CONTENT_MANIFEST: mockManifest() }) 143 | Object.assign(global, { __STATIC_CONTENT: mockKV(store) }) 144 | } 145 | 146 | export const sleep = (milliseconds: number) => { 147 | return new Promise((resolve) => setTimeout(resolve, milliseconds)) 148 | } 149 | -------------------------------------------------------------------------------- /src/test/getAssetFromKV-optional.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { mockRequestScope, mockGlobalScope, getEvent, sleep, mockKV, mockManifest } from '../mocks' 3 | mockGlobalScope() 4 | 5 | // manually reset manifest global, to test optional behaviour 6 | Object.assign(global, { __STATIC_CONTENT_MANIFEST: undefined }) 7 | 8 | import { getAssetFromKV, mapRequestToAsset } from '../index' 9 | 10 | test('getAssetFromKV return correct val from KV without manifest', async (t) => { 11 | mockRequestScope() 12 | // manually reset manifest global, to test optional behaviour 13 | Object.assign(global, { __STATIC_CONTENT_MANIFEST: undefined }) 14 | 15 | const event = getEvent(new Request('https://blah.com/key1.123HASHBROWN.txt')) 16 | const res = await getAssetFromKV(event) 17 | 18 | if (res) { 19 | t.is(await res.text(), 'val1') 20 | t.true(res.headers.get('content-type').includes('text')) 21 | } else { 22 | t.fail('Response was undefined') 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/test/getAssetFromKV.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { mockRequestScope, mockGlobalScope, getEvent, sleep, mockKV, mockManifest } from '../mocks' 3 | mockGlobalScope() 4 | 5 | import { getAssetFromKV, mapRequestToAsset } from '../index' 6 | import { KVError } from '../types' 7 | 8 | test('getAssetFromKV return correct val from KV and default caching', async (t) => { 9 | mockRequestScope() 10 | const event = getEvent(new Request('https://blah.com/key1.txt')) 11 | const res = await getAssetFromKV(event) 12 | 13 | if (res) { 14 | t.is(res.headers.get('cache-control'), null) 15 | t.is(res.headers.get('cf-cache-status'), 'MISS') 16 | t.is(await res.text(), 'val1') 17 | t.true(res.headers.get('content-type').includes('text')) 18 | } else { 19 | t.fail('Response was undefined') 20 | } 21 | }) 22 | test('getAssetFromKV evaluated the file matching the extensionless path first /client/ -> client', async (t) => { 23 | mockRequestScope() 24 | const event = getEvent(new Request(`https://foo.com/client/`)) 25 | const res = await getAssetFromKV(event) 26 | t.is(await res.text(), 'important file') 27 | t.true(res.headers.get('content-type').includes('text')) 28 | }) 29 | test('getAssetFromKV evaluated the file matching the extensionless path first /client -> client', async (t) => { 30 | mockRequestScope() 31 | const event = getEvent(new Request(`https://foo.com/client`)) 32 | const res = await getAssetFromKV(event) 33 | t.is(await res.text(), 'important file') 34 | t.true(res.headers.get('content-type').includes('text')) 35 | }) 36 | 37 | test('getAssetFromKV if not in asset manifest still returns nohash.txt', async (t) => { 38 | mockRequestScope() 39 | const event = getEvent(new Request('https://blah.com/nohash.txt')) 40 | const res = await getAssetFromKV(event) 41 | 42 | if (res) { 43 | t.is(await res.text(), 'no hash but still got some result') 44 | t.true(res.headers.get('content-type').includes('text')) 45 | } else { 46 | t.fail('Response was undefined') 47 | } 48 | }) 49 | 50 | test('getAssetFromKV if no asset manifest /client -> client fails', async (t) => { 51 | mockRequestScope() 52 | const event = getEvent(new Request(`https://foo.com/client`)) 53 | const error: KVError = await t.throwsAsync(getAssetFromKV(event, { ASSET_MANIFEST: {} })) 54 | t.is(error.status, 404) 55 | }) 56 | 57 | test('getAssetFromKV if sub/ -> sub/index.html served', async (t) => { 58 | mockRequestScope() 59 | const event = getEvent(new Request(`https://foo.com/sub`)) 60 | const res = await getAssetFromKV(event) 61 | if (res) { 62 | t.is(await res.text(), 'picturedis') 63 | } else { 64 | t.fail('Response was undefined') 65 | } 66 | }) 67 | 68 | test('getAssetFromKV gets index.html by default for / requests', async (t) => { 69 | mockRequestScope() 70 | const event = getEvent(new Request('https://blah.com/')) 71 | const res = await getAssetFromKV(event) 72 | 73 | if (res) { 74 | t.is(await res.text(), 'index.html') 75 | t.true(res.headers.get('content-type').includes('html')) 76 | } else { 77 | t.fail('Response was undefined') 78 | } 79 | }) 80 | 81 | test('getAssetFromKV non ASCII path support', async (t) => { 82 | mockRequestScope() 83 | const event = getEvent(new Request('https://blah.com/测试.html')) 84 | const res = await getAssetFromKV(event) 85 | 86 | if (res) { 87 | t.is(await res.text(), 'My filename is non-ascii') 88 | } else { 89 | t.fail('Response was undefined') 90 | } 91 | }) 92 | 93 | test('getAssetFromKV supports browser percent encoded URLs', async (t) => { 94 | mockRequestScope() 95 | const event = getEvent(new Request('https://example.com/%not-really-percent-encoded.html')) 96 | const res = await getAssetFromKV(event) 97 | 98 | if (res) { 99 | t.is(await res.text(), 'browser percent encoded') 100 | } else { 101 | t.fail('Response was undefined') 102 | } 103 | }) 104 | 105 | test('getAssetFromKV supports user percent encoded URLs', async (t) => { 106 | mockRequestScope() 107 | const event = getEvent(new Request('https://blah.com/%2F.html')) 108 | const res = await getAssetFromKV(event) 109 | 110 | if (res) { 111 | t.is(await res.text(), 'user percent encoded') 112 | } else { 113 | t.fail('Response was undefined') 114 | } 115 | }) 116 | 117 | test('getAssetFromKV only decode URL when necessary', async (t) => { 118 | mockRequestScope() 119 | const event1 = getEvent(new Request('https://blah.com/%E4%BD%A0%E5%A5%BD.html')) 120 | const event2 = getEvent(new Request('https://blah.com/你好.html')) 121 | const res1 = await getAssetFromKV(event1) 122 | const res2 = await getAssetFromKV(event2) 123 | 124 | if (res1 && res2) { 125 | t.is(await res1.text(), 'Im important') 126 | t.is(await res2.text(), 'Im important') 127 | } else { 128 | t.fail('Response was undefined') 129 | } 130 | }) 131 | 132 | test('getAssetFromKV Support for user decode url path', async (t) => { 133 | mockRequestScope() 134 | const event1 = getEvent(new Request('https://blah.com/%E4%BD%A0%E5%A5%BD/')) 135 | const event2 = getEvent(new Request('https://blah.com/你好/')) 136 | const res1 = await getAssetFromKV(event1) 137 | const res2 = await getAssetFromKV(event2) 138 | 139 | if (res1 && res2) { 140 | t.is(await res1.text(), 'My path is non-ascii') 141 | t.is(await res2.text(), 'My path is non-ascii') 142 | } else { 143 | t.fail('Response was undefined') 144 | } 145 | }) 146 | 147 | test('getAssetFromKV custom key modifier', async (t) => { 148 | mockRequestScope() 149 | const event = getEvent(new Request('https://blah.com/docs/sub/blah.png')) 150 | 151 | const customRequestMapper = (request: Request) => { 152 | let defaultModifiedRequest = mapRequestToAsset(request) 153 | 154 | let url = new URL(defaultModifiedRequest.url) 155 | url.pathname = url.pathname.replace('/docs', '') 156 | return new Request(url.toString(), request) 157 | } 158 | 159 | const res = await getAssetFromKV(event, { mapRequestToAsset: customRequestMapper }) 160 | 161 | if (res) { 162 | t.is(await res.text(), 'picturedis') 163 | } else { 164 | t.fail('Response was undefined') 165 | } 166 | }) 167 | 168 | test('getAssetFromKV request override with existing manifest file', async (t) => { 169 | // see https://github.com/cloudflare/kv-asset-handler/pull/159 for more info 170 | mockRequestScope() 171 | const event = getEvent(new Request('https://blah.com/image.png')) // real file in manifest 172 | 173 | const customRequestMapper = (request: Request) => { 174 | let defaultModifiedRequest = mapRequestToAsset(request) 175 | 176 | let url = new URL(defaultModifiedRequest.url) 177 | url.pathname = '/image.webp' // other different file in manifest 178 | return new Request(url.toString(), request) 179 | } 180 | 181 | const res = await getAssetFromKV(event, { mapRequestToAsset: customRequestMapper }) 182 | 183 | if (res) { 184 | t.is(await res.text(), 'imagewebp') 185 | } else { 186 | t.fail('Response was undefined') 187 | } 188 | }) 189 | 190 | test('getAssetFromKV when setting browser caching', async (t) => { 191 | mockRequestScope() 192 | const event = getEvent(new Request('https://blah.com/')) 193 | 194 | const res = await getAssetFromKV(event, { cacheControl: { browserTTL: 22 } }) 195 | 196 | if (res) { 197 | t.is(res.headers.get('cache-control'), 'max-age=22') 198 | } else { 199 | t.fail('Response was undefined') 200 | } 201 | }) 202 | 203 | test('getAssetFromKV when setting custom cache setting', async (t) => { 204 | mockRequestScope() 205 | const event1 = getEvent(new Request('https://blah.com/')) 206 | const event2 = getEvent(new Request('https://blah.com/key1.png?blah=34')) 207 | const cacheOnlyPngs = (req: Request) => { 208 | if (new URL(req.url).pathname.endsWith('.png')) 209 | return { 210 | browserTTL: 720, 211 | edgeTTL: 720, 212 | } 213 | else 214 | return { 215 | bypassCache: true, 216 | } 217 | } 218 | 219 | const res1 = await getAssetFromKV(event1, { cacheControl: cacheOnlyPngs }) 220 | const res2 = await getAssetFromKV(event2, { cacheControl: cacheOnlyPngs }) 221 | 222 | if (res1 && res2) { 223 | t.is(res1.headers.get('cache-control'), null) 224 | t.true(res2.headers.get('content-type').includes('png')) 225 | t.is(res2.headers.get('cache-control'), 'max-age=720') 226 | t.is(res2.headers.get('cf-cache-status'), 'MISS') 227 | } else { 228 | t.fail('Response was undefined') 229 | } 230 | }) 231 | test('getAssetFromKV caches on two sequential requests', async (t) => { 232 | mockRequestScope() 233 | const resourceKey = 'cache.html' 234 | const resourceVersion = JSON.parse(mockManifest())[resourceKey] 235 | const event1 = getEvent(new Request(`https://blah.com/${resourceKey}`)) 236 | const event2 = getEvent( 237 | new Request(`https://blah.com/${resourceKey}`, { 238 | headers: { 239 | 'if-none-match': `"${resourceVersion}"`, 240 | }, 241 | }), 242 | ) 243 | 244 | const res1 = await getAssetFromKV(event1, { cacheControl: { edgeTTL: 720, browserTTL: 720 } }) 245 | await sleep(1) 246 | const res2 = await getAssetFromKV(event2) 247 | 248 | if (res1 && res2) { 249 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 250 | t.is(res1.headers.get('cache-control'), 'max-age=720') 251 | t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED') 252 | } else { 253 | t.fail('Response was undefined') 254 | } 255 | }) 256 | test('getAssetFromKV does not store max-age on two sequential requests', async (t) => { 257 | mockRequestScope() 258 | const resourceKey = 'cache.html' 259 | const resourceVersion = JSON.parse(mockManifest())[resourceKey] 260 | const event1 = getEvent(new Request(`https://blah.com/${resourceKey}`)) 261 | const event2 = getEvent( 262 | new Request(`https://blah.com/${resourceKey}`, { 263 | headers: { 264 | 'if-none-match': `"${resourceVersion}"`, 265 | }, 266 | }), 267 | ) 268 | 269 | const res1 = await getAssetFromKV(event1, { cacheControl: { edgeTTL: 720 } }) 270 | await sleep(100) 271 | const res2 = await getAssetFromKV(event2) 272 | 273 | if (res1 && res2) { 274 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 275 | t.is(res1.headers.get('cache-control'), null) 276 | t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED') 277 | t.is(res2.headers.get('cache-control'), null) 278 | } else { 279 | t.fail('Response was undefined') 280 | } 281 | }) 282 | 283 | test('getAssetFromKV does not cache on Cloudflare when bypass cache set', async (t) => { 284 | mockRequestScope() 285 | const event = getEvent(new Request('https://blah.com/')) 286 | 287 | const res = await getAssetFromKV(event, { cacheControl: { bypassCache: true } }) 288 | 289 | if (res) { 290 | t.is(res.headers.get('cache-control'), null) 291 | t.is(res.headers.get('cf-cache-status'), null) 292 | } else { 293 | t.fail('Response was undefined') 294 | } 295 | }) 296 | 297 | test('getAssetFromKV with no trailing slash on root', async (t) => { 298 | mockRequestScope() 299 | const event = getEvent(new Request('https://blah.com')) 300 | const res = await getAssetFromKV(event) 301 | if (res) { 302 | t.is(await res.text(), 'index.html') 303 | } else { 304 | t.fail('Response was undefined') 305 | } 306 | }) 307 | 308 | test('getAssetFromKV with no trailing slash on a subdirectory', async (t) => { 309 | mockRequestScope() 310 | const event = getEvent(new Request('https://blah.com/sub/blah.png')) 311 | const res = await getAssetFromKV(event) 312 | if (res) { 313 | t.is(await res.text(), 'picturedis') 314 | } else { 315 | t.fail('Response was undefined') 316 | } 317 | }) 318 | 319 | test('getAssetFromKV no result throws an error', async (t) => { 320 | mockRequestScope() 321 | const event = getEvent(new Request('https://blah.com/random')) 322 | const error: KVError = await t.throwsAsync(getAssetFromKV(event)) 323 | t.is(error.status, 404) 324 | }) 325 | test('getAssetFromKV TTls set to null should not cache on browser or edge', async (t) => { 326 | mockRequestScope() 327 | const event = getEvent(new Request('https://blah.com/')) 328 | 329 | const res1 = await getAssetFromKV(event, { cacheControl: { browserTTL: null, edgeTTL: null } }) 330 | await sleep(100) 331 | const res2 = await getAssetFromKV(event, { cacheControl: { browserTTL: null, edgeTTL: null } }) 332 | 333 | if (res1 && res2) { 334 | t.is(res1.headers.get('cf-cache-status'), null) 335 | t.is(res1.headers.get('cache-control'), null) 336 | t.is(res2.headers.get('cf-cache-status'), null) 337 | t.is(res2.headers.get('cache-control'), null) 338 | } else { 339 | t.fail('Response was undefined') 340 | } 341 | }) 342 | test('getAssetFromKV passing in a custom NAMESPACE serves correct asset', async (t) => { 343 | mockRequestScope() 344 | let CUSTOM_NAMESPACE = mockKV({ 345 | 'key1.123HASHBROWN.txt': 'val1', 346 | }) 347 | Object.assign(global, { CUSTOM_NAMESPACE }) 348 | const event = getEvent(new Request('https://blah.com/')) 349 | const res = await getAssetFromKV(event) 350 | if (res) { 351 | t.is(await res.text(), 'index.html') 352 | t.true(res.headers.get('content-type').includes('html')) 353 | } else { 354 | t.fail('Response was undefined') 355 | } 356 | }) 357 | test('getAssetFromKV when custom namespace without the asset should fail', async (t) => { 358 | mockRequestScope() 359 | let CUSTOM_NAMESPACE = mockKV({ 360 | 'key5.123HASHBROWN.txt': 'customvalu', 361 | }) 362 | 363 | const event = getEvent(new Request('https://blah.com')) 364 | const error: KVError = await t.throwsAsync( 365 | getAssetFromKV(event, { ASSET_NAMESPACE: CUSTOM_NAMESPACE }), 366 | ) 367 | t.is(error.status, 404) 368 | }) 369 | test('getAssetFromKV when namespace not bound fails', async (t) => { 370 | mockRequestScope() 371 | var MY_CUSTOM_NAMESPACE = undefined 372 | Object.assign(global, { MY_CUSTOM_NAMESPACE }) 373 | 374 | const event = getEvent(new Request('https://blah.com/')) 375 | const error: KVError = await t.throwsAsync( 376 | getAssetFromKV(event, { ASSET_NAMESPACE: MY_CUSTOM_NAMESPACE }), 377 | ) 378 | t.is(error.status, 500) 379 | }) 380 | 381 | test('getAssetFromKV when if-none-match === active resource version, should revalidate', async (t) => { 382 | mockRequestScope() 383 | const resourceKey = 'key1.png' 384 | const resourceVersion = JSON.parse(mockManifest())[resourceKey] 385 | const event1 = getEvent(new Request(`https://blah.com/${resourceKey}`)) 386 | const event2 = getEvent( 387 | new Request(`https://blah.com/${resourceKey}`, { 388 | headers: { 389 | 'if-none-match': `W/"${resourceVersion}"`, 390 | }, 391 | }), 392 | ) 393 | 394 | const res1 = await getAssetFromKV(event1, { cacheControl: { edgeTTL: 720 } }) 395 | await sleep(100) 396 | const res2 = await getAssetFromKV(event2) 397 | 398 | if (res1 && res2) { 399 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 400 | t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED') 401 | } else { 402 | t.fail('Response was undefined') 403 | } 404 | }) 405 | 406 | test('getAssetFromKV when if-none-match equals etag of stale resource then should bypass cache', async (t) => { 407 | mockRequestScope() 408 | const resourceKey = 'key1.png' 409 | const resourceVersion = JSON.parse(mockManifest())[resourceKey] 410 | const req1 = new Request(`https://blah.com/${resourceKey}`, { 411 | headers: { 412 | 'if-none-match': `"${resourceVersion}"`, 413 | }, 414 | }) 415 | const req2 = new Request(`https://blah.com/${resourceKey}`, { 416 | headers: { 417 | 'if-none-match': `"${resourceVersion}-another-version"`, 418 | }, 419 | }) 420 | const event = getEvent(req1) 421 | const event2 = getEvent(req2) 422 | const res1 = await getAssetFromKV(event, { cacheControl: { edgeTTL: 720 } }) 423 | const res2 = await getAssetFromKV(event) 424 | const res3 = await getAssetFromKV(event2) 425 | if (res1 && res2 && res3) { 426 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 427 | t.is(res2.headers.get('etag'), `W/${req1.headers.get('if-none-match')}`) 428 | t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED') 429 | t.not(res3.headers.get('etag'), req2.headers.get('if-none-match')) 430 | t.is(res3.headers.get('cf-cache-status'), 'MISS') 431 | } else { 432 | t.fail('Response was undefined') 433 | } 434 | }) 435 | test('getAssetFromKV when resource in cache, etag should be weakened before returned to eyeball', async (t) => { 436 | mockRequestScope() 437 | const resourceKey = 'key1.png' 438 | const resourceVersion = JSON.parse(mockManifest())[resourceKey] 439 | const req1 = new Request(`https://blah.com/${resourceKey}`, { 440 | headers: { 441 | 'if-none-match': `"${resourceVersion}"`, 442 | }, 443 | }) 444 | const event = getEvent(req1) 445 | const res1 = await getAssetFromKV(event, { cacheControl: { edgeTTL: 720 } }) 446 | const res2 = await getAssetFromKV(event) 447 | if (res1 && res2) { 448 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 449 | t.is(res2.headers.get('etag'), `W/${req1.headers.get('if-none-match')}`) 450 | } else { 451 | t.fail('Response was undefined') 452 | } 453 | }) 454 | test('getAssetFromKV should support weak etag override of resource', async (t) => { 455 | mockRequestScope() 456 | const resourceKey = 'key1.png' 457 | const resourceVersion = JSON.parse(mockManifest())[resourceKey] 458 | const req1 = new Request(`https://blah-weak.com/${resourceKey}`, { 459 | headers: { 460 | 'if-none-match': `W/"${resourceVersion}"`, 461 | }, 462 | }) 463 | const req2 = new Request(`https://blah-weak.com/${resourceKey}`, { 464 | headers: { 465 | 'if-none-match': `"${resourceVersion}"`, 466 | }, 467 | }) 468 | const req3 = new Request(`https://blah-weak.com/${resourceKey}`, { 469 | headers: { 470 | 'if-none-match': `"${resourceVersion}-another-version"`, 471 | }, 472 | }) 473 | const event1 = getEvent(req1) 474 | const event2 = getEvent(req2) 475 | const event3 = getEvent(req3) 476 | const res1 = await getAssetFromKV(event1, { defaultETag: 'weak' }) 477 | const res2 = await getAssetFromKV(event2, { defaultETag: 'weak' }) 478 | const res3 = await getAssetFromKV(event3, { defaultETag: 'weak' }) 479 | if (res1 && res2 && res3) { 480 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 481 | t.is(res1.headers.get('etag'), req1.headers.get('if-none-match')) 482 | t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED') 483 | t.is(res2.headers.get('etag'), `W/${req2.headers.get('if-none-match')}`) 484 | t.is(res3.headers.get('cf-cache-status'), 'MISS') 485 | t.not(res3.headers.get('etag'), req2.headers.get('if-none-match')) 486 | } else { 487 | t.fail('Response was undefined') 488 | } 489 | }) 490 | 491 | test('getAssetFromKV if-none-match not sent but resource in cache, should return cache hit 200 OK', async (t) => { 492 | const resourceKey = 'cache.html' 493 | const event = getEvent(new Request(`https://blah.com/${resourceKey}`)) 494 | const res1 = await getAssetFromKV(event, { cacheControl: { edgeTTL: 720 } }) 495 | await sleep(1) 496 | const res2 = await getAssetFromKV(event) 497 | if (res1 && res2) { 498 | t.is(res1.headers.get('cf-cache-status'), 'MISS') 499 | t.is(res1.headers.get('cache-control'), null) 500 | t.is(res2.status, 200) 501 | t.is(res2.headers.get('cf-cache-status'), 'HIT') 502 | } else { 503 | t.fail('Response was undefined') 504 | } 505 | }) 506 | 507 | test('getAssetFromKV if range request submitted and resource in cache, request fulfilled', async (t) => { 508 | const resourceKey = 'cache.html' 509 | const event1 = getEvent(new Request(`https://blah.com/${resourceKey}`)) 510 | const event2 = getEvent( 511 | new Request(`https://blah.com/${resourceKey}`, { headers: { range: 'bytes=0-10' } }), 512 | ) 513 | const res1 = getAssetFromKV(event1, { cacheControl: { edgeTTL: 720 } }) 514 | await res1 515 | await sleep(2) 516 | const res2 = await getAssetFromKV(event2) 517 | if (res2.headers.has('content-range')) { 518 | t.is(res2.status, 206) 519 | } else { 520 | t.fail('Response was undefined') 521 | } 522 | }) 523 | 524 | test.todo('getAssetFromKV when body not empty, should invoke .cancel()') 525 | -------------------------------------------------------------------------------- /src/test/mapRequestToAsset.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { mockRequestScope, mockGlobalScope } from '../mocks' 3 | mockGlobalScope() 4 | 5 | import { mapRequestToAsset } from '../index' 6 | 7 | test('mapRequestToAsset() correctly changes /about -> /about/index.html', async (t) => { 8 | mockRequestScope() 9 | let path = '/about' 10 | let request = new Request(`https://foo.com${path}`) 11 | let newRequest = mapRequestToAsset(request) 12 | t.is(newRequest.url, request.url + '/index.html') 13 | }) 14 | 15 | test('mapRequestToAsset() correctly changes /about/ -> /about/index.html', async (t) => { 16 | mockRequestScope() 17 | let path = '/about/' 18 | let request = new Request(`https://foo.com${path}`) 19 | let newRequest = mapRequestToAsset(request) 20 | t.is(newRequest.url, request.url + 'index.html') 21 | }) 22 | 23 | test('mapRequestToAsset() correctly changes /about.me/ -> /about.me/index.html', async (t) => { 24 | mockRequestScope() 25 | let path = '/about.me/' 26 | let request = new Request(`https://foo.com${path}`) 27 | let newRequest = mapRequestToAsset(request) 28 | t.is(newRequest.url, request.url + 'index.html') 29 | }) 30 | 31 | test('mapRequestToAsset() correctly changes /about -> /about/default.html', async (t) => { 32 | mockRequestScope() 33 | let path = '/about' 34 | let request = new Request(`https://foo.com${path}`) 35 | let newRequest = mapRequestToAsset(request, { defaultDocument: 'default.html' }) 36 | t.is(newRequest.url, request.url + '/default.html') 37 | }) 38 | -------------------------------------------------------------------------------- /src/test/serveSinglePageApp.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { mockRequestScope, mockGlobalScope } from '../mocks' 3 | mockGlobalScope() 4 | 5 | import { serveSinglePageApp } from '../index' 6 | 7 | function testRequest(path: string) { 8 | mockRequestScope() 9 | let url = new URL('https://example.com') 10 | url.pathname = path 11 | let request = new Request(url.toString()) 12 | 13 | return request 14 | } 15 | 16 | test('serveSinglePageApp returns root asset path when request path ends in .html', async (t) => { 17 | let path = '/foo/thing.html' 18 | let request = testRequest(path) 19 | 20 | let expected_request = testRequest('/index.html') 21 | let actual_request = serveSinglePageApp(request) 22 | 23 | t.deepEqual(expected_request, actual_request) 24 | }) 25 | 26 | test('serveSinglePageApp returns root asset path when request path does not have extension', async (t) => { 27 | let path = '/foo/thing' 28 | let request = testRequest(path) 29 | 30 | let expected_request = testRequest('/index.html') 31 | let actual_request = serveSinglePageApp(request) 32 | 33 | t.deepEqual(expected_request, actual_request) 34 | }) 35 | 36 | test('serveSinglePageApp returns requested asset when request path has non-html extension', async (t) => { 37 | let path = '/foo/thing.js' 38 | let request = testRequest(path) 39 | 40 | let expected_request = request 41 | let actual_request = serveSinglePageApp(request) 42 | 43 | t.deepEqual(expected_request, actual_request) 44 | }) 45 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type CacheControl = { 2 | browserTTL: number 3 | edgeTTL: number 4 | bypassCache: boolean 5 | } 6 | 7 | export type AssetManifestType = Record 8 | 9 | export type Options = { 10 | cacheControl: ((req: Request) => Partial) | Partial 11 | ASSET_NAMESPACE: any 12 | ASSET_MANIFEST: AssetManifestType | string 13 | mapRequestToAsset?: (req: Request, options?: Partial) => Request 14 | defaultMimeType: string 15 | defaultDocument: string 16 | pathIsEncoded: boolean 17 | defaultETag: 'strong' | 'weak' 18 | } 19 | 20 | export class KVError extends Error { 21 | constructor(message?: string, status: number = 500) { 22 | super(message) 23 | // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html 24 | Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain 25 | this.name = KVError.name // stack traces display correctly now 26 | this.status = status 27 | } 28 | status: number 29 | } 30 | export class MethodNotAllowedError extends KVError { 31 | constructor(message: string = `Not a valid request method`, status: number = 405) { 32 | super(message, status) 33 | } 34 | } 35 | export class NotFoundError extends KVError { 36 | constructor(message: string = `Not Found`, status: number = 404) { 37 | super(message, status) 38 | } 39 | } 40 | export class InternalError extends KVError { 41 | constructor(message: string = `Internal Error in KV Asset Handler`, status: number = 500) { 42 | super(message, status) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "noImplicitAny": true, 5 | "target": "ES2017", 6 | "allowJs": false, 7 | "lib": ["WebWorker", "ES5", "ScriptHost"], 8 | "module": "commonjs", 9 | "moduleResolution": "node" 10 | }, 11 | "include": ["./src/*.ts", "./src/**/*.ts", "./test/**/*.ts", "./test/*.ts", "./src/types.d.ts"], 12 | "exclude": ["node_modules/", "dist/"] 13 | } 14 | --------------------------------------------------------------------------------